comparison roundup/configuration.py @ 2770:cdf6787ffeda

implement getopt(). support config file paths as well as directory paths for loading. UserConfig: defaults were added to all config sections. filter them out.
author Alexander Smishlajev <a1s@users.sourceforge.net>
date Sun, 17 Oct 2004 17:35:32 +0000
parents eccb8e15a83f
children f965de0c1e75
comparison
equal deleted inserted replaced
2769:b4fb224300b1 2770:cdf6787ffeda
1 # Roundup Issue Tracker configuration support 1 # Roundup Issue Tracker configuration support
2 # 2 #
3 # $Id: configuration.py,v 1.16 2004-07-28 09:46:58 a1s Exp $ 3 # $Id: configuration.py,v 1.17 2004-10-17 17:35:32 a1s Exp $
4 # 4 #
5 __docformat__ = "restructuredtext" 5 __docformat__ = "restructuredtext"
6 6
7 import getopt
7 import imp 8 import imp
8 import os 9 import os
9 import time 10 import time
10 import ConfigParser 11 import ConfigParser
11 12
301 Paths may be either absolute or relative to the HOME. 302 Paths may be either absolute or relative to the HOME.
302 303
303 """ 304 """
304 305
305 class_description = "The path may be either absolute or relative\n" \ 306 class_description = "The path may be either absolute or relative\n" \
306 " to the directory containig this config file." 307 "to the directory containig this config file."
307 308
308 def get(self): 309 def get(self):
309 _val = Option.get(self) 310 _val = Option.get(self)
310 if _val and not os.path.isabs(_val): 311 if _val and not os.path.isabs(_val):
311 _val = os.path.join(self.config["HOME"], _val) 312 _val = os.path.join(self.config["HOME"], _val)
586 section_descriptions = None 587 section_descriptions = None
587 # lists of option names for each section, in order 588 # lists of option names for each section, in order
588 section_options = None 589 section_options = None
589 # mapping from option names and aliases to Option instances 590 # mapping from option names and aliases to Option instances
590 options = None 591 options = None
591 592 # actual name of the config file. set on load.
592 def __init__(self, home_dir=None, layout=None): 593 filepath = os.path.join(HOME, INI_FILE)
594
595 def __init__(self, config_path=None, layout=None):
593 """Initialize confing instance 596 """Initialize confing instance
594 597
595 Parameters: 598 Parameters:
596 home_dir: 599 config_path:
597 optional configuration directory. 600 optional directory or file name of the config file.
598 If passed, load the config from that directory 601 If passed, load the config after processing layout (if any).
599 after processing config layout (if any). 602 If config_path is a directory name, use default base name
603 of the config file.
600 layout: 604 layout:
601 optional configuration layout, a sequence of 605 optional configuration layout, a sequence of
602 section definitions suitable for .add_section() 606 section definitions suitable for .add_section()
603 607
604 """ 608 """
609 self.options = {} 613 self.options = {}
610 # add options from the layout structure 614 # add options from the layout structure
611 if layout: 615 if layout:
612 for section in layout: 616 for section in layout:
613 self.add_section(*section) 617 self.add_section(*section)
614 if home_dir is not None: 618 if config_path is not None:
615 self.load(home_dir) 619 self.load(config_path)
616 620
617 def add_section(self, section, options, description=None): 621 def add_section(self, section, options, description=None):
618 """Define new config section 622 """Define new config section
619 623
620 Parameters: 624 Parameters:
696 def reset(self): 700 def reset(self):
697 """Set all options to their default values""" 701 """Set all options to their default values"""
698 for _option in self.items(): 702 for _option in self.items():
699 _option.reset() 703 _option.reset()
700 704
701 # This is a placeholder. TBD.
702 # Meant for commandline tools. 705 # Meant for commandline tools.
703 # Allows automatic creation of configuration files like this: 706 # Allows automatic creation of configuration files like this:
704 # roundup-server -p 8017 -u roundup --save-config 707 # roundup-server -p 8017 -u roundup --save-config
705 def getopt(self, args, **options): 708 def getopt(self, args, short_options="", long_options=(),
709 config_load_options=("C", "config"), **options
710 ):
706 """Apply options specified in command line arguments. 711 """Apply options specified in command line arguments.
707 712
708 Parameters: 713 Parameters:
709 args: 714 args:
710 command line to parse (sys.argv[1:]) 715 command line to parse (sys.argv[1:])
716 short_options:
717 optional string of letters for command line options
718 that are not config options
719 long_options:
720 optional list of names for long options
721 that are not config options
722 config_load_options:
723 two-element sequence (letter, long_option) defining
724 the options for config file. If unset, don't load
725 config file; otherwise config file is read prior
726 to applying other options. Short option letter
727 must not have a colon and long_option name must
728 not have an equal sign or '--' prefix.
711 options: 729 options:
712 mapping from option names to command line option specs. 730 mapping from option names to command line option specs.
713 e.g. server_port="p:", server_user="u:" 731 e.g. server_port="p:", server_user="u:"
714 Names are forced to lower case for commandline parsing 732 Names are forced to lower case for commandline parsing
715 (long options) and to upper case to find config options. 733 (long options) and to upper case to find config options.
718 736
719 Return value: same as for python standard getopt(), except that 737 Return value: same as for python standard getopt(), except that
720 processed options are removed from returned option list. 738 processed options are removed from returned option list.
721 739
722 """ 740 """
741 # take a copy of long_options
742 long_options = list(long_options)
743 # build option lists
744 cfg_names = {}
745 booleans = []
746 for (name, letter) in options.items():
747 cfg_name = name.upper()
748 short_opt = "-" + letter[0]
749 name = name.lower().replace("_", "-")
750 cfg_names.update({short_opt: cfg_name, "--" + name: cfg_name})
751
752 short_options += letter
753 if letter[-1] == ":":
754 long_options.append(name + "=")
755 else:
756 booleans.append(short_opt)
757 long_options.append(name)
758
759 if config_load_options:
760 short_options += config_load_options[0] + ":"
761 long_options.append(config_load_options[1] + "=")
762 # compute names that will be searched in getopt return value
763 config_load_options = (
764 "-" + config_load_options[0],
765 "--" + config_load_options[1],
766 )
767 # parse command line arguments
768 optlist, args = getopt.getopt(args, short_options, long_options)
769 # load config file if requested
770 if config_load_options:
771 for option in optlist:
772 if option[0] in config_load_options:
773 self.load_ini(option[1])
774 optlist.remove(option)
775 break
776 # apply options
777 extra_options = []
778 for (opt, arg) in optlist:
779 if (opt in booleans): # and not arg
780 arg = "yes"
781 try:
782 name = cfg_names[opt]
783 except KeyError:
784 extra_options.append((opt, arg))
785 else:
786 self[name] = arg
787 return (extra_options, args)
723 788
724 # option and section locators (used in option access methods) 789 # option and section locators (used in option access methods)
725 790
726 def _get_option(self, name): 791 def _get_option(self, name):
727 try: 792 try:
744 for option in self.items(): 809 for option in self.items():
745 if not option.isset(): 810 if not option.isset():
746 need_set.setdefault(option.section, []).append(option.setting) 811 need_set.setdefault(option.section, []).append(option.setting)
747 return need_set 812 return need_set
748 813
814 def _adjust_options(self, config):
815 """Load ad-hoc option definitions from ConfigParser instance."""
816 pass
817
818 def _get_name(self):
819 """Return the service name for config file heading"""
820 return ""
821
749 # file operations 822 # file operations
750 823
751 def load_ini(self, home_dir, defaults=None): 824 def load_ini(self, config_path, defaults=None):
752 """Set options from config.ini file in given home_dir 825 """Set options from config.ini file in given home_dir
753 826
754 Parameters: 827 Parameters:
755 home_dir: 828 config_path:
756 config home directory 829 directory or file name of the config file.
830 If config_path is a directory name, use default
831 base name of the config file
757 defaults: 832 defaults:
758 optional dictionary of defaults for ConfigParser 833 optional dictionary of defaults for ConfigParser
759 834
760 Note: if home_dir does not contain config.ini file, 835 Note: if home_dir does not contain config.ini file,
761 no error is raised. Config will be reset to defaults. 836 no error is raised. Config will be reset to defaults.
762 837
763 """ 838 """
839 if os.path.isdir(config_path):
840 home_dir = config_path
841 config_path = os.path.join(config_path, self.INI_FILE)
842 else:
843 home_dir = os.path.dirname(config_path)
764 # parse the file 844 # parse the file
765 config_defaults = {"HOME": home_dir} 845 config_defaults = {"HOME": home_dir}
766 if defaults: 846 if defaults:
767 config_defaults.update(defaults) 847 config_defaults.update(defaults)
768 config = ConfigParser.ConfigParser(config_defaults) 848 config = ConfigParser.ConfigParser(config_defaults)
769 config.read([os.path.join(home_dir, self.INI_FILE)]) 849 config.read([os.path.join(home_dir, self.INI_FILE)])
770 # .ini file loaded ok. set the options, starting from HOME 850 # .ini file loaded ok.
851 self.HOME = home_dir
852 self.filepath = config_path
853 self._adjust_options(config)
854 # set the options, starting from HOME
771 self.reset() 855 self.reset()
772 self.HOME = home_dir
773 for option in self.items(): 856 for option in self.items():
774 option.load_ini(config) 857 option.load_ini(config)
775 858
776 def load(self, home_dir): 859 def load(self, home_dir):
777 """Load configuration settings from home_dir""" 860 """Load configuration settings from home_dir"""
787 If the file to write already exists, it is saved with '.bak' 870 If the file to write already exists, it is saved with '.bak'
788 extension. 871 extension.
789 872
790 """ 873 """
791 if ini_file is None: 874 if ini_file is None:
792 ini_file = os.path.join(self.HOME, self.INI_FILE) 875 ini_file = self.filepath
793 _tmp_file = os.path.splitext(ini_file)[0] 876 _tmp_file = os.path.splitext(ini_file)[0]
794 _bak_file = _tmp_file + ".bak" 877 _bak_file = _tmp_file + ".bak"
795 _tmp_file = _tmp_file + ".tmp" 878 _tmp_file = _tmp_file + ".tmp"
796 _fp = file(_tmp_file, "wt") 879 _fp = file(_tmp_file, "wt")
797 _fp.write("# %s configuration file\n" % self["TRACKER_NAME"]) 880 _fp.write("# %s configuration file\n" % self._get_name())
798 _fp.write("# Autogenerated at %s\n" % time.asctime()) 881 _fp.write("# Autogenerated at %s\n" % time.asctime())
799 need_set = self._get_unset_options() 882 need_set = self._get_unset_options()
800 if need_set: 883 if need_set:
801 _fp.write("\n# WARNING! Following options need adjustments:\n") 884 _fp.write("\n# WARNING! Following options need adjustments:\n")
802 for section, options in need_set.items(): 885 for section, options in need_set.items():
889 Options are created on the fly for each setting present in the 972 Options are created on the fly for each setting present in the
890 config file. 973 config file.
891 974
892 """ 975 """
893 976
894 def load_ini(self, home_dir, defaults=None): 977 def _adjust_options(self, config):
895 """Load options from config.ini file in given home_dir 978 # config defaults appear in all sections.
896 979 # we'll need to filter them out.
897 Parameters: 980 defaults = config.defaults().keys()
898 home_dir:
899 config home directory
900 defaults:
901 optional dictionary of defaults for ConfigParser
902
903 Options are automatically created as they are read
904 from the config file.
905
906 Note: if home_dir does not contain config.ini file,
907 no error is raised. Config will be reset to defaults.
908
909 """
910 # parse the file
911 config_defaults = {"HOME": home_dir}
912 if defaults:
913 config_defaults.update(defaults)
914 config = ConfigParser.ConfigParser(defaults)
915 config.read([os.path.join(home_dir, self.INI_FILE)])
916 # .ini file loaded ok.
917 self.HOME = home_dir
918 # see what options are already defined and add missing ones 981 # see what options are already defined and add missing ones
919 preset = [(option.section, option.setting) for option in self.items()] 982 preset = [(option.section, option.setting) for option in self.items()]
920 for section in config.sections(): 983 for section in config.sections():
921 for name in config.options(section): 984 for name in config.options(section):
922 if (section, name) not in preset: 985 if ((section, name) not in preset) \
986 and (name not in defaults):
923 self.add_option(Option(self, section, name)) 987 self.add_option(Option(self, section, name))
924 # set the options
925 self.reset()
926 for option in self.items():
927 option.load_ini(config)
928 988
929 class CoreConfig(Config): 989 class CoreConfig(Config):
930 990
931 """Roundup instance configuration. 991 """Roundup instance configuration.
932 992
965 settings = need_set["mail"] 1025 settings = need_set["mail"]
966 settings.remove("password") 1026 settings.remove("password")
967 if not settings: 1027 if not settings:
968 del need_set["mail"] 1028 del need_set["mail"]
969 return need_set 1029 return need_set
1030
1031 def _get_name(self):
1032 return self["TRACKER_NAME"]
970 1033
971 def reset(self): 1034 def reset(self):
972 Config.reset(self) 1035 Config.reset(self)
973 if self.ext: 1036 if self.ext:
974 self.ext.reset() 1037 self.ext.reset()

Roundup Issue Tracker: http://roundup-tracker.org/