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: Addressed comments Created June 24, 2015, 8:49 a.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
10 from StringIO import StringIO
9 11
10 class Item(tuple): 12 class Item(tuple):
11 def __new__(cls, name, value, source): 13 def __new__(cls, name, value, source):
12 result = super(Item, cls).__new__(cls, (name, value)) 14 result = super(Item, cls).__new__(cls, (name, value))
13 result.source = source 15 result.source = source
14 return result 16 return result
15 17
16 class DiffForUnknownOptionError(ConfigParser.Error): 18 class DiffForUnknownOptionError(ConfigParser.Error):
17 def __init__(self, option, section): 19 def __init__(self, option, section):
18 ConfigParser.Error.__init__(self, 'Failed to apply diff for unknown option ' 20 ConfigParser.Error.__init__(self, 'Failed to apply diff for unknown option '
(...skipping 16 matching lines...) Expand all
35 whitespace-separated lists given by an inherited option: 37 whitespace-separated lists given by an inherited option:
36 38
37 [section] 39 [section]
38 opt1 += foo 40 opt1 += foo
39 opt2 -= bar 41 opt2 -= bar
40 42
41 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
42 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,
43 longer chains are disallowed to deal with circular references. 45 longer chains are disallowed to deal with circular references.
44 46
45 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
46 constructed: a file path has to be passed, this file is assumed to be 48 reading. Also, ChainedConfigParser data is read-only. An additional
47 encoded as UTF-8. Also, ChainedConfigParser data is read-only and the 49 option_source(section, option) method is provided to get the path
48 options are case-sensitive. An additional option_source(section, option) 50 of the configuration file defining this option (for relative paths).
49 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
50 option (for relative paths). Items returned by the items() function also 52 serving the same purpose.
51 have a source attribute serving the same purpose.
52 ''' 53 '''
53 54
54 def __init__(self): 55 def __init__(self):
55 ConfigParser.SafeConfigParser.__init__(self) 56 ConfigParser.SafeConfigParser.__init__(self)
56 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
57 67
58 def _get_parser_chain(self, parser, filename): 68 def _get_parser_chain(self, parser, filename):
59 parsers = [] 69 parsers = []
60 70
61 while True: 71 while True:
62 parsers.insert(0, (parser, filename)) 72 parsers.insert(0, (parser, filename))
63 73
64 try: 74 try:
65 inherit = parser.get('default', 'inherit') 75 inherit = parser.get('default', 'inherit')
66 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): 76 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
67 return parsers 77 return parsers
68 78
69 filename = os.path.join(os.path.dirname(filename), *inherit.split('/')) 79 filename = os.path.join(os.path.dirname(filename), *inherit.split('/'))
70 parser = ConfigParser.SafeConfigParser() 80 parser = self._make_parser(filename)
71 parser.read(filename)
72 81
73 def _apply_diff(self, section, option, value): 82 def _apply_diff(self, section, option, value):
74 is_addition = option.endswith('+') 83 is_addition = option.endswith('+')
75 is_diff = is_addition or option.endswith('-') 84 is_diff = is_addition or option.endswith('-')
76 85
77 if is_diff: 86 if is_diff:
78 option = option[:-1].rstrip() 87 option = option[:-1].rstrip()
79 try: 88 try:
80 orig_value = self.get(section, option) 89 orig_value = self.get(section, option)
81 except ConfigParser.NoOptionError: 90 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) 91 raise DiffForUnknownOptionError(option, section)
83 92
84 orig_values = orig_value.split() 93 orig_values = orig_value.split()
85 diff_values = value.split() 94 diff_values = value.split()
86 95
87 if is_addition: 96 if is_addition:
88 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]
89 else: 98 else:
90 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]
91 100
92 value = ' '.join(new_values) 101 value = ' '.join(new_values)
93 102
94 return is_diff, option, value 103 return is_diff, option, value
95 104
96 def _process_parsers(self, parsers): 105 def _process_parsers(self, parsers):
97 for parser, filename in parsers: 106 for parser, filename in parsers:
98 for section in parser.sections(): 107 for section in parser.sections():
99 if not self.has_section(section): 108 if not self.has_section(section):
100 try: 109 try:
101 self.add_section(section) 110 ConfigParser.SafeConfigParser.add_section(self, section)
102 except ValueError: 111 except ValueError:
103 # add_section() hardcodes 'default' and raises a ValueError if 112 # add_section() hardcodes 'default' and raises a ValueError if
104 # you try to add a section called like that (case insensitive). 113 # you try to add a section called like that (case insensitive).
105 # This bug has been fixed in Python 3. 114 # This bug has been fixed in Python 3.
106 self._sections[section] = self._dict() 115 ConfigParser.SafeConfigParser.readfp(self, StringIO('[%s]' % section ))
107 116
108 for option, value in parser.items(section): 117 for option, value in parser.items(section):
109 is_diff, option, value = self._apply_diff(section, option, value) 118 is_diff, option, value = self._apply_diff(section, option, value)
110 ConfigParser.SafeConfigParser.set(self, section, option, value) 119 ConfigParser.SafeConfigParser.set(self, section, option, value)
111 120
112 if not is_diff: 121 if not is_diff:
113 self._origin[(section, option)] = filename 122 self._origin[(section, self.optionxform(option))] = filename
114 123
115 def read(self, filenames): 124 def read(self, filenames):
116 if isinstance(filenames, basestring): 125 if isinstance(filenames, basestring):
117 filenames = [filenames] 126 filenames = [filenames]
118 127
119 read_ok = [] 128 read_ok = []
120 for filename in filenames: 129 for filename in filenames:
121 parser = ConfigParser.SafeConfigParser() 130 try:
122 read_ok.extend(parser.read(filename)) 131 parser = self._make_parser(filename)
132 except IOError:
133 continue
123 self._process_parsers(self._get_parser_chain(parser, filename)) 134 self._process_parsers(self._get_parser_chain(parser, filename))
135 read_ok.append(filename)
124 136
125 return read_ok 137 return read_ok
126 138
127 def items(self, section, *args, **kwargs): 139 def items(self, section, *args, **kwargs):
128 items = [] 140 items = []
129 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):
130 items.append(Item(option, value, self._origin[(section, option)])) 142 items.append(Item(
143 option, value,
144 self._origin[(section, self.optionxform(option))]
145 ))
131 return items 146 return items
132 147
133 def option_source(self, section, option): 148 def option_source(self, section, option):
149 option = self.optionxform(option)
134 try: 150 try:
135 return self._origin[(section, option)] 151 return self._origin[(section, option)]
136 except KeyError: 152 except KeyError:
137 if not self.has_section(section): 153 if not self.has_section(section):
138 raise ConfigParser.NoSectionError(section) 154 raise ConfigParser.NoSectionError(section)
139 raise ConfigParser.NoOptionError(option, section) 155 raise ConfigParser.NoOptionError(option, section)
140 156
141 def readfp(self, fp, filename=None): 157 def readfp(self, fp, filename=None):
142 raise NotImplementedError 158 raise NotImplementedError
143 159
144 def set(self, section, option, value=None): 160 def set(self, section, option, value=None):
145 raise NotImplementedError 161 raise NotImplementedError
146 162
163 def add_section(self, section):
164 raise NotImplementedError
165
147 def remove_option(self, section, option): 166 def remove_option(self, section, option):
148 raise NotImplementedError 167 raise NotImplementedError
149 168
150 def remove_section(self, section): 169 def remove_section(self, section):
151 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