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

Side by Side Diff: chainedconfigparser.py

Issue 29319007: Issue 2711 - Refactored ChainedConfigParser, allowing manipulation of list items (Closed)
Patch Set: Addressed comments Created June 24, 2015, 8:49 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | packager.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
8 9
9 class Item(tuple): 10 class Item(tuple):
10 def __new__(cls, name, value, source): 11 def __new__(cls, name, value, source):
11 result = super(Item, cls).__new__(cls, (name, value)) 12 result = super(Item, cls).__new__(cls, (name, value))
12 result.source = source 13 result.source = source
13 return result 14 return result
14 15
15 class ChainedConfigParser: 16 class DiffForUnknownOptionError(ConfigParser.Error):
16 """ 17 def __init__(self, option, section):
18 ConfigParser.Error.__init__(self, 'Failed to apply diff for unknown option '
19 '%r in section %r' % (option, section))
20 self.option = option
21 self.section = section
22 self.args = (option, section)
23
24 class ChainedConfigParser(ConfigParser.SafeConfigParser):
25 '''
17 This class provides essentially the same interfaces as SafeConfigParser but 26 This class provides essentially the same interfaces as SafeConfigParser but
18 allows chaining configuration files so that one config file provides the 27 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 28 default values for the other. To specify the config file to inherit from
20 a config file needs to contain the following option: 29 a config file needs to contain the following option:
21 30
22 [default] 31 [default]
23 inherit = foo/bar.config 32 inherit = foo/bar.config
24 33
34 It is also possible to add values to or remove values from
35 whitespace-separated lists given by an inherited option:
36
37 [section]
38 opt1 += foo
39 opt2 -= bar
40
25 The value of the inherit option has to be a relative path with forward 41 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, 42 slashes as delimiters. Up to 5 configuration files can be chained this way,
27 longer chains are disallowed to deal with circular references. 43 longer chains are disallowed to deal with circular references.
28 44
29 A main API difference to SafeConfigParser is the way a class instance is 45 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 46 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 47 encoded as UTF-8. Also, ChainedConfigParser data is read-only and the
32 options are case-sensitive. An additional option_source(section, option) 48 options are case-sensitive. An additional option_source(section, option)
33 method is provided to get the path of the configuration file defining this 49 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 50 option (for relative paths). Items returned by the items() function also
35 have a source attribute serving the same purpose. 51 have a source attribute serving the same purpose.
36 """ 52 '''
37 53
38 def __init__(self, path): 54 def __init__(self):
39 self.chain = [] 55 ConfigParser.SafeConfigParser.__init__(self)
40 self.read_path(path) 56 self._origin = {}
41 57
42 def read_path(self, path): 58 def _get_parser_chain(self, parser, filename):
43 if len(self.chain) >= 5: 59 parsers = []
44 raise Exception('Too much inheritance in config files')
45 60
46 config = ConfigParser.SafeConfigParser() 61 while True:
47 config.optionxform = str 62 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 63
54 if config.has_section('default') and config.has_option('default', 'inherit') : 64 try:
55 parts = config.get('default', 'inherit').split('/') 65 inherit = parser.get('default', 'inherit')
56 defaults_path = os.path.join(os.path.dirname(path), *parts) 66 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
57 self.read_path(defaults_path) 67 return parsers
58 68
59 def defaults(self): 69 filename = os.path.join(os.path.dirname(filename), *inherit.split('/'))
60 result = {} 70 parser = ConfigParser.SafeConfigParser()
61 for config in reverse(self.chain): 71 parser.read(filename)
62 for key, value in config.defaults().iteritems():
63 result[key] = value
64 return result
65 72
66 def sections(self): 73 def _apply_diff(self, section, option, value):
67 result = set() 74 is_addition = option.endswith('+')
68 for config in self.chain: 75 is_diff = is_addition or option.endswith('-')
69 for section in config.sections():
70 result.add(section)
71 return list(result)
72 76
73 def has_section(self, section): 77 if is_diff:
74 for config in self.chain: 78 option = option[:-1].rstrip()
75 if config.has_section(section): 79 try:
76 return True 80 orig_value = self.get(section, option)
77 return False 81 except ConfigParser.NoOptionError:
Wladimir Palant 2015/06/25 14:11:40 What about ConfigParser.NoSectionError?
Sebastian Noack 2015/06/25 16:06:28 We already make sure to create the section if it d
82 raise DiffForUnknownOptionError(option, section)
78 83
79 def options(self, section): 84 orig_values = orig_value.split()
80 result = set() 85 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 86
87 def has_option(self, section, option): 87 if is_addition:
88 for config in self.chain: 88 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): 89 else:
90 return True 90 new_values = [v for v in orig_values if v not in diff_values]
91 return False
92 91
93 def get(self, section, option): 92 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 93
99 def items(self, section): 94 return is_diff, option, value
100 seen = set() 95
101 result = [] 96 def _process_parsers(self, parsers):
102 for config in self.chain: 97 for parser, filename in parsers:
103 if config.has_section(section): 98 for section in parser.sections():
104 for name, value in config.items(section): 99 if not self.has_section(section):
105 if name not in seen: 100 try:
106 seen.add(name) 101 self.add_section(section)
107 result.append(Item(name, value, config.source_path)) 102 except ValueError:
108 return result 103 # add_section() hardcodes 'default' and raises a ValueError if
104 # you try to add a section called like that (case insensitive).
105 # This bug has been fixed in Python 3.
106 self._sections[section] = self._dict()
107
108 for option, value in parser.items(section):
109 is_diff, option, value = self._apply_diff(section, option, value)
110 ConfigParser.SafeConfigParser.set(self, section, option, value)
111
112 if not is_diff:
113 self._origin[(section, option)] = filename
114
115 def read(self, filenames):
116 if isinstance(filenames, basestring):
117 filenames = [filenames]
118
119 read_ok = []
120 for filename in filenames:
121 parser = ConfigParser.SafeConfigParser()
122 read_ok.extend(parser.read(filename))
123 self._process_parsers(self._get_parser_chain(parser, filename))
124
125 return read_ok
126
127 def items(self, section, *args, **kwargs):
128 items = []
129 for option, value in ConfigParser.SafeConfigParser.items(self, section, *arg s, **kwargs):
130 items.append(Item(option, value, self._origin[(section, option)]))
131 return items
109 132
110 def option_source(self, section, option): 133 def option_source(self, section, option):
111 for config in self.chain: 134 try:
112 if config.has_section(section) and config.has_option(section, option): 135 return self._origin[(section, option)]
113 return config.source_path 136 except KeyError:
114 raise ConfigParser.NoOptionError(option, section) 137 if not self.has_section(section):
138 raise ConfigParser.NoSectionError(section)
139 raise ConfigParser.NoOptionError(option, section)
140
141 def readfp(self, fp, filename=None):
142 raise NotImplementedError
143
144 def set(self, section, option, value=None):
145 raise NotImplementedError
146
147 def remove_option(self, section, option):
148 raise NotImplementedError
149
150 def remove_section(self, section):
151 raise NotImplementedError
OLDNEW
« no previous file with comments | « no previous file | packager.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld