Mercurial > p > roundup > code
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() |
