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

Delta Between Two Patch Sets: chainedconfigparser.py

Issue 29319007: Issue 2711 - Refactored ChainedConfigParser, allowing manipulation of list items (Closed)
Left Patch Set: Don't rely on internal APIs to work around add_section("default") bug Created June 26, 2015, 1:37 p.m.
Right Patch Set: Addressed comments Created July 7, 2015, 3:18 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | packager.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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 7 import os
8 import io
8 import ConfigParser 9 import ConfigParser
9 from StringIO import StringIO 10 from StringIO import StringIO
10 11
11 class Item(tuple): 12 class Item(tuple):
12 def __new__(cls, name, value, source): 13 def __new__(cls, name, value, source):
13 result = super(Item, cls).__new__(cls, (name, value)) 14 result = super(Item, cls).__new__(cls, (name, value))
14 result.source = source 15 result.source = source
15 return result 16 return result
16 17
17 class DiffForUnknownOptionError(ConfigParser.Error): 18 class DiffForUnknownOptionError(ConfigParser.Error):
(...skipping 18 matching lines...) Expand all
36 whitespace-separated lists given by an inherited option: 37 whitespace-separated lists given by an inherited option:
37 38
38 [section] 39 [section]
39 opt1 += foo 40 opt1 += foo
40 opt2 -= bar 41 opt2 -= bar
41 42
42 The value of the inherit option has to be a relative path with forward 43 The value of the inherit option has to be a relative path with forward
43 slashes as delimiters. Up to 5 configuration files can be chained this way, 44 slashes as delimiters. Up to 5 configuration files can be chained this way,
44 longer chains are disallowed to deal with circular references. 45 longer chains are disallowed to deal with circular references.
45 46
46 A main API difference to SafeConfigParser is the way a class instance is 47 As opposed to SafeConfigParser, files are decoded as UTF-8 while
47 constructed: a file path has to be passed, this file is assumed to be 48 reading. Also, ChainedConfigParser data is read-only. An additional
48 encoded as UTF-8. Also, ChainedConfigParser data is read-only and the 49 option_source(section, option) method is provided to get the path
49 options are case-sensitive. An additional option_source(section, option) 50 of the configuration file defining this option (for relative paths).
50 method is provided to get the path of the configuration file defining this 51 Items returned by the items() function also have a source attribute
51 option (for relative paths). Items returned by the items() function also 52 serving the same purpose.
52 have a source attribute serving the same purpose.
53 ''' 53 '''
54 54
55 def __init__(self): 55 def __init__(self):
56 ConfigParser.SafeConfigParser.__init__(self) 56 ConfigParser.SafeConfigParser.__init__(self)
57 self._origin = {} 57 self._origin = {}
58
59 def _make_parser(self, filename):
60 parser = ConfigParser.SafeConfigParser()
61 parser.optionxform = lambda option: option
62
63 with io.open(filename, encoding='utf-8') as file:
64 parser.readfp(file, filename)
65
66 return parser
58 67
59 def _get_parser_chain(self, parser, filename): 68 def _get_parser_chain(self, parser, filename):
60 parsers = [] 69 parsers = []
61 70
62 while True: 71 while True:
63 parsers.insert(0, (parser, filename)) 72 parsers.insert(0, (parser, filename))
64 73
65 try: 74 try:
66 inherit = parser.get('default', 'inherit') 75 inherit = parser.get('default', 'inherit')
67 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): 76 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
68 return parsers 77 return parsers
69 78
70 filename = os.path.join(os.path.dirname(filename), *inherit.split('/')) 79 filename = os.path.join(os.path.dirname(filename), *inherit.split('/'))
71 parser = ConfigParser.SafeConfigParser() 80 parser = self._make_parser(filename)
72 parser.read(filename)
73 81
74 def _apply_diff(self, section, option, value): 82 def _apply_diff(self, section, option, value):
75 is_addition = option.endswith('+') 83 is_addition = option.endswith('+')
76 is_diff = is_addition or option.endswith('-') 84 is_diff = is_addition or option.endswith('-')
77 85
78 if is_diff: 86 if is_diff:
79 option = option[:-1].rstrip() 87 option = option[:-1].rstrip()
80 try: 88 try:
81 orig_value = self.get(section, option) 89 orig_value = self.get(section, option)
82 except ConfigParser.NoOptionError: 90 except ConfigParser.NoOptionError:
83 raise DiffForUnknownOptionError(option, section) 91 raise DiffForUnknownOptionError(option, section)
84 92
85 orig_values = orig_value.split() 93 orig_values = orig_value.split()
86 diff_values = value.split() 94 diff_values = value.split()
87 95
88 if is_addition: 96 if is_addition:
89 new_values = orig_values + [v for v in diff_values if v not in orig_valu es] 97 new_values = orig_values + [v for v in diff_values if v not in orig_valu es]
90 else: 98 else:
91 new_values = [v for v in orig_values if v not in diff_values] 99 new_values = [v for v in orig_values if v not in diff_values]
92 100
93 value = ' '.join(new_values) 101 value = ' '.join(new_values)
94 102
95 return is_diff, option, value 103 return is_diff, option, value
96 104
97 def _process_parsers(self, parsers): 105 def _process_parsers(self, parsers):
98 for parser, filename in parsers: 106 for parser, filename in parsers:
99 for section in parser.sections(): 107 for section in parser.sections():
100 if not self.has_section(section): 108 if not self.has_section(section):
101 try: 109 try:
102 self.add_section(section) 110 ConfigParser.SafeConfigParser.add_section(self, section)
103 except ValueError: 111 except ValueError:
104 # add_section() hardcodes 'default' and raises a ValueError if 112 # add_section() hardcodes 'default' and raises a ValueError if
105 # you try to add a section called like that (case insensitive). 113 # you try to add a section called like that (case insensitive).
106 # This bug has been fixed in Python 3. 114 # This bug has been fixed in Python 3.
107 ConfigParser.SafeConfigParser.readfp(self, StringIO('[%s]' % section )) 115 ConfigParser.SafeConfigParser.readfp(self, StringIO('[%s]' % section ))
108 116
109 for option, value in parser.items(section): 117 for option, value in parser.items(section):
110 is_diff, option, value = self._apply_diff(section, option, value) 118 is_diff, option, value = self._apply_diff(section, option, value)
111 ConfigParser.SafeConfigParser.set(self, section, option, value) 119 ConfigParser.SafeConfigParser.set(self, section, option, value)
112 120
113 if not is_diff: 121 if not is_diff:
114 self._origin[(section, option)] = filename 122 self._origin[(section, self.optionxform(option))] = filename
115 123
116 def read(self, filenames): 124 def read(self, filenames):
117 if isinstance(filenames, basestring): 125 if isinstance(filenames, basestring):
118 filenames = [filenames] 126 filenames = [filenames]
119 127
120 read_ok = [] 128 read_ok = []
121 for filename in filenames: 129 for filename in filenames:
122 parser = ConfigParser.SafeConfigParser() 130 try:
123 read_ok.extend(parser.read(filename)) 131 parser = self._make_parser(filename)
132 except IOError:
133 continue
124 self._process_parsers(self._get_parser_chain(parser, filename)) 134 self._process_parsers(self._get_parser_chain(parser, filename))
135 read_ok.append(filename)
125 136
126 return read_ok 137 return read_ok
127 138
128 def items(self, section, *args, **kwargs): 139 def items(self, section, *args, **kwargs):
129 items = [] 140 items = []
130 for option, value in ConfigParser.SafeConfigParser.items(self, section, *arg s, **kwargs): 141 for option, value in ConfigParser.SafeConfigParser.items(self, section, *arg s, **kwargs):
131 items.append(Item(option, value, self._origin[(section, option)])) 142 items.append(Item(
143 option, value,
144 self._origin[(section, self.optionxform(option))]
145 ))
132 return items 146 return items
133 147
134 def option_source(self, section, option): 148 def option_source(self, section, option):
149 option = self.optionxform(option)
135 try: 150 try:
136 return self._origin[(section, option)] 151 return self._origin[(section, option)]
137 except KeyError: 152 except KeyError:
138 if not self.has_section(section): 153 if not self.has_section(section):
139 raise ConfigParser.NoSectionError(section) 154 raise ConfigParser.NoSectionError(section)
140 raise ConfigParser.NoOptionError(option, section) 155 raise ConfigParser.NoOptionError(option, section)
141 156
142 def readfp(self, fp, filename=None): 157 def readfp(self, fp, filename=None):
143 raise NotImplementedError 158 raise NotImplementedError
144 159
145 def set(self, section, option, value=None): 160 def set(self, section, option, value=None):
146 raise NotImplementedError 161 raise NotImplementedError
147 162
163 def add_section(self, section):
164 raise NotImplementedError
165
148 def remove_option(self, section, option): 166 def remove_option(self, section, option):
149 raise NotImplementedError 167 raise NotImplementedError
150 168
151 def remove_section(self, section): 169 def remove_section(self, section):
152 raise NotImplementedError 170 raise NotImplementedError
LEFTRIGHT
« no previous file | packager.py » ('j') | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld