Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: chainedconfigparser.py

Issue 29319007: Issue 2711 - Refactored ChainedConfigParser, allowing manipulation of list items (Closed)
Patch Set: Created June 22, 2015, 9:58 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | packager.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chainedconfigparser.py
===================================================================
--- a/chainedconfigparser.py
+++ b/chainedconfigparser.py
@@ -4,7 +4,9 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-import os, codecs, ConfigParser
+import os
+import re
+import ConfigParser
class Item(tuple):
def __new__(cls, name, value, source):
@@ -12,8 +14,8 @@
result.source = source
return result
-class ChainedConfigParser:
- """
+class ChainedConfigParser(ConfigParser.SafeConfigParser):
+ '''
This class provides essentially the same interfaces as SafeConfigParser but
allows chaining configuration files so that one config file provides the
default values for the other. To specify the config file to inherit from
@@ -33,82 +35,73 @@
method is provided to get the path of the configuration file defining this
option (for relative paths). Items returned by the items() function also
have a source attribute serving the same purpose.
Wladimir Palant 2015/06/23 09:43:36 Extend documentation to mention the new += and -=
Sebastian Noack 2015/06/24 08:51:09 Done.
- """
+ '''
- def __init__(self, path):
- self.chain = []
- self.read_path(path)
+ def __init__(self):
+ ConfigParser.SafeConfigParser.__init__(self)
+ self._origin = {}
- def read_path(self, path):
- if len(self.chain) >= 5:
- raise Exception('Too much inheritance in config files')
+ def _get_parser_chain(self, file, filename):
+ parsers = []
- config = ConfigParser.SafeConfigParser()
- config.optionxform = str
- config.source_path = path
- handle = codecs.open(path, 'rb', encoding='utf-8')
- config.readfp(handle)
- handle.close()
- self.chain.append(config)
+ parser = ConfigParser.SafeConfigParser()
+ parser._read(file, filename)
Wladimir Palant 2015/06/23 09:43:35 Please use public API here: parser.readfp(file,
Sebastian Noack 2015/06/24 08:51:10 This code has been gone while addressing the comme
- if config.has_section('default') and config.has_option('default', 'inherit'):
- parts = config.get('default', 'inherit').split('/')
- defaults_path = os.path.join(os.path.dirname(path), *parts)
- self.read_path(defaults_path)
+ while True:
+ parsers.insert(0, (parser, filename))
- def defaults(self):
- result = {}
- for config in reverse(self.chain):
- for key, value in config.defaults().iteritems():
- result[key] = value
- return result
+ try:
+ inherit = parser.get('default', 'inherit')
+ except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+ return parsers
- def sections(self):
- result = set()
- for config in self.chain:
- for section in config.sections():
- result.add(section)
- return list(result)
+ filename = os.path.join(os.path.dirname(filename), *inherit.split('/'))
+ parser = ConfigParser.SafeConfigParser()
+ parser.read(filename)
- def has_section(self, section):
- for config in self.chain:
- if config.has_section(section):
- return True
- return False
+ def _apply_diff(self, section, option, value):
+ addition = option.endswith('+')
+ removal = option.endswith('-')
- def options(self, section):
- result = set()
- for config in self.chain:
- if config.has_section(section):
- for option in config.options(section):
- result.add(option)
- return list(result)
+ if addition or removal:
+ option = option[:-1].rstrip()
+ old_value = self.get(section, option)
Wladimir Palant 2015/06/23 09:43:36 This will throw when trying to change an option th
Sebastian Noack 2015/06/24 08:51:09 Done.
- def has_option(self, section, option):
- for config in self.chain:
- if config.has_section(section) and config.has_option(section, option):
- return True
- return False
+ if addition:
+ value = '%s %s' % (old_value, value)
Wladimir Palant 2015/06/23 09:43:35 I don't think that we want duplicate values - we a
Sebastian Noack 2015/06/24 08:51:10 Done.
+ elif removal:
Wladimir Palant 2015/06/23 09:43:35 Nit: no need for elif, can be simply else. But I d
Sebastian Noack 2015/06/24 08:51:09 Done.
+ value = re.sub(r'\b(?:%s)\b\s*' % '|'.join(map(re.escape, value.split())), '', old_value).rstrip()
Wladimir Palant 2015/06/23 09:43:35 I don't think that regular expressions are the rig
Sebastian Noack 2015/06/24 08:51:09 Done.
- def get(self, section, option):
- for config in self.chain:
- if config.has_section(section) and config.has_option(section, option):
- return config.get(section, option)
- raise ConfigParser.NoOptionError(option, section)
+ return option, value
- def items(self, section):
- seen = set()
- result = []
- for config in self.chain:
- if config.has_section(section):
- for name, value in config.items(section):
- if name not in seen:
- seen.add(name)
- result.append(Item(name, value, config.source_path))
- return result
+ def _read(self, file, filename):
Wladimir Palant 2015/06/23 09:43:35 Is it really a good idea to override private metho
Sebastian Noack 2015/06/24 08:51:10 Overriding read() and readfp() would require quite
+ parsers = self._get_parser_chain(file, filename)
+
+ for parser, filename in parsers:
+ for section in parser.sections():
+ for option, value in parser.items(section):
+ option, value = self._apply_diff(section, option, value)
+ try:
+ self.set(section, option, value)
+ except ConfigParser.NoSectionError:
+ try:
+ self.add_section(section)
+ except ValueError:
+ # add_section() hardcodes 'default' and raises a ValueError if
+ # you try to add a section called like that (case insensitive).
+ # This bug has been fixed in Python 3.
+ self._sections[section] = self._dict()
Wladimir Palant 2015/06/23 09:43:36 I cannot say that I like this hack. How about simp
Sebastian Noack 2015/06/24 08:51:09 "default" != "DEFAULT". The latter is handled spec
Wladimir Palant 2015/06/25 14:11:40 I see. Still, should we access private variables i
Sebastian Noack 2015/06/25 16:06:28 The except block will only be reached in Python ve
Wladimir Palant 2015/06/25 16:12:28 That is: every Python version we support. And I'm
Sebastian Noack 2015/06/25 23:05:31 In case I didn't stress this enough, this is a bug
Wladimir Palant 2015/06/26 13:17:21 It doesn't matter what you call it - it's document
Sebastian Noack 2015/06/26 13:37:53 So your concerns here are only about using interna
+ self.set(section, option, value)
+ self._origin[(section, option)] = filename
Wladimir Palant 2015/06/23 09:43:35 Ok, we have a problem right here... Consider the f
Sebastian Noack 2015/06/24 08:51:09 I first thought that we can simply rely on the sou
+
+ def items(self, section, *args, **kwargs):
+ items = []
+ for option, value in ConfigParser.SafeConfigParser.items(self, section, *args, **kwargs):
+ items.append(Item(option, value, self._origin[(section, option)]))
+ return items
def option_source(self, section, option):
- for config in self.chain:
- if config.has_section(section) and config.has_option(section, option):
- return config.source_path
- raise ConfigParser.NoOptionError(option, section)
+ try:
+ return self._origin[(section, option)]
+ except KeyError:
+ raise ConfigParser.NoOptionError(option, section)
« no previous file with comments | « no previous file | packager.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld