| OLD | NEW | 
|    1 # coding: utf-8 |    1 # coding: utf-8 | 
|    2  |    2  | 
|    3 # This Source Code Form is subject to the terms of the Mozilla Public |    3 # This Source Code Form is subject to the terms of the Mozilla Public | 
|    4 # License, v. 2.0. If a copy of the MPL was not distributed with this |    4 # License, v. 2.0. If a copy of the MPL was not distributed with this | 
|    5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |    5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. | 
|    6  |    6  | 
|    7 import os, codecs, ConfigParser |    7 import os | 
 |    8 import ConfigParser | 
 |    9 from StringIO import StringIO | 
|    8  |   10  | 
|    9 class Item(tuple): |   11 class Item(tuple): | 
|   10   def __new__(cls, name, value, source): |   12   def __new__(cls, name, value, source): | 
|   11     result = super(Item, cls).__new__(cls, (name, value)) |   13     result = super(Item, cls).__new__(cls, (name, value)) | 
|   12     result.source = source |   14     result.source = source | 
|   13     return result |   15     return result | 
|   14  |   16  | 
|   15 class ChainedConfigParser: |   17 class DiffForUnknownOptionError(ConfigParser.Error): | 
|   16   """ |   18   def __init__(self, option, section): | 
 |   19     ConfigParser.Error.__init__(self, 'Failed to apply diff for unknown option ' | 
 |   20                                       '%r in section %r' % (option, section)) | 
 |   21     self.option = option | 
 |   22     self.section = section | 
 |   23     self.args = (option, section) | 
 |   24  | 
 |   25 class ChainedConfigParser(ConfigParser.SafeConfigParser): | 
 |   26   ''' | 
|   17     This class provides essentially the same interfaces as SafeConfigParser but |   27     This class provides essentially the same interfaces as SafeConfigParser but | 
|   18     allows chaining configuration files so that one config file provides the |   28     allows chaining configuration files so that one config file provides the | 
|   19     default values for the other. To specify the config file to inherit from |   29     default values for the other. To specify the config file to inherit from | 
|   20     a config file needs to contain the following option: |   30     a config file needs to contain the following option: | 
|   21  |   31  | 
|   22     [default] |   32     [default] | 
|   23     inherit = foo/bar.config |   33     inherit = foo/bar.config | 
|   24  |   34  | 
 |   35     It is also possible to add values to or remove values from | 
 |   36     whitespace-separated lists given by an inherited option: | 
 |   37  | 
 |   38     [section] | 
 |   39     opt1 += foo | 
 |   40     opt2 -= bar | 
 |   41  | 
|   25     The value of the inherit option has to be a relative path with forward |   42     The value of the inherit option has to be a relative path with forward | 
|   26     slashes as delimiters. Up to 5 configuration files can be chained this way, |   43     slashes as delimiters. Up to 5 configuration files can be chained this way, | 
|   27     longer chains are disallowed to deal with circular references. |   44     longer chains are disallowed to deal with circular references. | 
|   28  |   45  | 
|   29     A main API difference to SafeConfigParser is the way a class instance is |   46     A main API difference to SafeConfigParser is the way a class instance is | 
|   30     constructed: a file path has to be passed, this file is assumed to be |   47     constructed: a file path has to be passed, this file is assumed to be | 
|   31     encoded as UTF-8. Also, ChainedConfigParser data is read-only and the |   48     encoded as UTF-8. Also, ChainedConfigParser data is read-only and the | 
|   32     options are case-sensitive. An additional option_source(section, option) |   49     options are case-sensitive. An additional option_source(section, option) | 
|   33     method is provided to get the path of the configuration file defining this |   50     method is provided to get the path of the configuration file defining this | 
|   34     option (for relative paths). Items returned by the items() function also |   51     option (for relative paths). Items returned by the items() function also | 
|   35     have a source attribute serving the same purpose. |   52     have a source attribute serving the same purpose. | 
|   36   """ |   53   ''' | 
|   37  |   54  | 
|   38   def __init__(self, path): |   55   def __init__(self): | 
|   39     self.chain = [] |   56     ConfigParser.SafeConfigParser.__init__(self) | 
|   40     self.read_path(path) |   57     self._origin = {} | 
|   41  |   58  | 
|   42   def read_path(self, path): |   59   def _get_parser_chain(self, parser, filename): | 
|   43     if len(self.chain) >= 5: |   60     parsers = [] | 
|   44       raise Exception('Too much inheritance in config files') |  | 
|   45  |   61  | 
|   46     config = ConfigParser.SafeConfigParser() |   62     while True: | 
|   47     config.optionxform = str |   63       parsers.insert(0, (parser, filename)) | 
|   48     config.source_path = path |  | 
|   49     handle = codecs.open(path, 'rb', encoding='utf-8') |  | 
|   50     config.readfp(handle) |  | 
|   51     handle.close() |  | 
|   52     self.chain.append(config) |  | 
|   53  |   64  | 
|   54     if config.has_section('default') and config.has_option('default', 'inherit')
     : |   65       try: | 
|   55       parts = config.get('default', 'inherit').split('/') |   66         inherit = parser.get('default', 'inherit') | 
|   56       defaults_path = os.path.join(os.path.dirname(path), *parts) |   67       except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): | 
|   57       self.read_path(defaults_path) |   68         return parsers | 
|   58  |   69  | 
|   59   def defaults(self): |   70       filename = os.path.join(os.path.dirname(filename), *inherit.split('/')) | 
|   60     result = {} |   71       parser = ConfigParser.SafeConfigParser() | 
|   61     for config in reverse(self.chain): |   72       parser.read(filename) | 
|   62       for key, value in config.defaults().iteritems(): |  | 
|   63         result[key] = value |  | 
|   64     return result |  | 
|   65  |   73  | 
|   66   def sections(self): |   74   def _apply_diff(self, section, option, value): | 
|   67     result = set() |   75     is_addition = option.endswith('+') | 
|   68     for config in self.chain: |   76     is_diff = is_addition or option.endswith('-') | 
|   69       for section in config.sections(): |  | 
|   70         result.add(section) |  | 
|   71     return list(result) |  | 
|   72  |   77  | 
|   73   def has_section(self, section): |   78     if is_diff: | 
|   74     for config in self.chain: |   79       option = option[:-1].rstrip() | 
|   75       if config.has_section(section): |   80       try: | 
|   76         return True |   81         orig_value = self.get(section, option) | 
|   77     return False |   82       except ConfigParser.NoOptionError: | 
 |   83         raise DiffForUnknownOptionError(option, section) | 
|   78  |   84  | 
|   79   def options(self, section): |   85       orig_values = orig_value.split() | 
|   80     result = set() |   86       diff_values = value.split() | 
|   81     for config in self.chain: |  | 
|   82       if config.has_section(section): |  | 
|   83         for option in config.options(section): |  | 
|   84           result.add(option) |  | 
|   85     return list(result) |  | 
|   86  |   87  | 
|   87   def has_option(self, section, option): |   88       if is_addition: | 
|   88     for config in self.chain: |   89         new_values = orig_values + [v for v in diff_values if v not in orig_valu
     es] | 
|   89       if config.has_section(section) and config.has_option(section, option): |   90       else: | 
|   90         return True |   91         new_values = [v for v in orig_values if v not in diff_values] | 
|   91     return False |  | 
|   92  |   92  | 
|   93   def get(self, section, option): |   93       value = ' '.join(new_values) | 
|   94     for config in self.chain: |  | 
|   95       if config.has_section(section) and config.has_option(section, option): |  | 
|   96         return config.get(section, option) |  | 
|   97     raise ConfigParser.NoOptionError(option, section) |  | 
|   98  |   94  | 
|   99   def items(self, section): |   95     return is_diff, option, value | 
|  100     seen = set() |   96  | 
|  101     result = [] |   97   def _process_parsers(self, parsers): | 
|  102     for config in self.chain: |   98     for parser, filename in parsers: | 
|  103       if config.has_section(section): |   99       for section in parser.sections(): | 
|  104         for name, value in config.items(section): |  100         if not self.has_section(section): | 
|  105           if name not in seen: |  101           try: | 
|  106             seen.add(name) |  102             self.add_section(section) | 
|  107             result.append(Item(name, value, config.source_path)) |  103           except ValueError: | 
|  108     return result |  104             # add_section() hardcodes 'default' and raises a ValueError if | 
 |  105             # you try to add a section called like that (case insensitive). | 
 |  106             # This bug has been fixed in Python 3. | 
 |  107             ConfigParser.SafeConfigParser.readfp(self, StringIO('[%s]' % section
     )) | 
 |  108  | 
 |  109         for option, value in parser.items(section): | 
 |  110           is_diff, option, value = self._apply_diff(section, option, value) | 
 |  111           ConfigParser.SafeConfigParser.set(self, section, option, value) | 
 |  112  | 
 |  113           if not is_diff: | 
 |  114             self._origin[(section, option)] = filename | 
 |  115  | 
 |  116   def read(self, filenames): | 
 |  117     if isinstance(filenames, basestring): | 
 |  118       filenames = [filenames] | 
 |  119  | 
 |  120     read_ok = [] | 
 |  121     for filename in filenames: | 
 |  122       parser = ConfigParser.SafeConfigParser() | 
 |  123       read_ok.extend(parser.read(filename)) | 
 |  124       self._process_parsers(self._get_parser_chain(parser, filename)) | 
 |  125  | 
 |  126     return read_ok | 
 |  127  | 
 |  128   def items(self, section, *args, **kwargs): | 
 |  129     items = [] | 
 |  130     for option, value in ConfigParser.SafeConfigParser.items(self, section, *arg
     s, **kwargs): | 
 |  131       items.append(Item(option, value, self._origin[(section, option)])) | 
 |  132     return items | 
|  109  |  133  | 
|  110   def option_source(self, section, option): |  134   def option_source(self, section, option): | 
|  111     for config in self.chain: |  135     try: | 
|  112       if config.has_section(section) and config.has_option(section, option): |  136       return self._origin[(section, option)] | 
|  113         return config.source_path |  137     except KeyError: | 
|  114     raise ConfigParser.NoOptionError(option, section) |  138       if not self.has_section(section): | 
 |  139         raise ConfigParser.NoSectionError(section) | 
 |  140       raise ConfigParser.NoOptionError(option, section) | 
 |  141  | 
 |  142   def readfp(self, fp, filename=None): | 
 |  143     raise NotImplementedError | 
 |  144  | 
 |  145   def set(self, section, option, value=None): | 
 |  146     raise NotImplementedError | 
 |  147  | 
 |  148   def remove_option(self, section, option): | 
 |  149     raise NotImplementedError | 
 |  150  | 
 |  151   def remove_section(self, section): | 
 |  152     raise NotImplementedError | 
| OLD | NEW |