| Left: | ||
| Right: | 
| LEFT | RIGHT | 
|---|---|
| 1 # This Source Code Form is subject to the terms of the Mozilla Public | 1 # This Source Code Form is subject to the terms of the Mozilla Public | 
| 2 # License, v. 2.0. If a copy of the MPL was not distributed with this | 2 # License, v. 2.0. If a copy of the MPL was not distributed with this | 
| 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. | 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. | 
| 4 | 4 | 
| 5 import os | 5 import os | 
| 6 import io | 6 import io | 
| 7 import ConfigParser | 7 import ConfigParser | 
| 8 from StringIO import StringIO | 8 from StringIO import StringIO | 
| 9 from collections import OrderedDict | |
| 10 | 9 | 
| 11 | 10 | 
| 12 class Item(tuple): | 11 class Item(tuple): | 
| 13 def __new__(cls, name, value, source): | 12 def __new__(cls, name, value, source): | 
| 14 result = super(Item, cls).__new__(cls, (name, value)) | 13 result = super(Item, cls).__new__(cls, (name, value)) | 
| 15 result.source = source | 14 result.source = source | 
| 16 return result | 15 return result | 
| 17 | 16 | 
| 18 | 17 | 
| 19 class DiffForUnknownOptionError(ConfigParser.Error): | 18 class DiffForUnknownOptionError(ConfigParser.Error): | 
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 87 is_addition = option.endswith('+') | 86 is_addition = option.endswith('+') | 
| 88 is_diff = is_addition or option.endswith('-') | 87 is_diff = is_addition or option.endswith('-') | 
| 89 | 88 | 
| 90 if is_diff: | 89 if is_diff: | 
| 91 option = option[:-1].rstrip() | 90 option = option[:-1].rstrip() | 
| 92 try: | 91 try: | 
| 93 orig_value = self.get(section, option) | 92 orig_value = self.get(section, option) | 
| 94 except ConfigParser.NoOptionError: | 93 except ConfigParser.NoOptionError: | 
| 95 raise DiffForUnknownOptionError(option, section) | 94 raise DiffForUnknownOptionError(option, section) | 
| 96 | 95 | 
| 97 orig_values = orig_value.split() | 96 orig_values = orig_value.splitlines() | 
| 98 diff_values = value.split() | 97 diff_values = value.splitlines() | 
| 99 | 98 | 
| 100 if is_addition: | 99 if is_addition: | 
| 101 new_values = orig_values + [v for v in diff_values if v not in o rig_values] | 100 new_values = orig_values + [v for v in diff_values if v not in o rig_values] | 
| 102 else: | 101 else: | 
| 103 new_values = [v for v in orig_values if v not in diff_values] | 102 new_values = [v for v in orig_values if v not in diff_values] | 
| 104 | 103 | 
| 105 value = ' '.join(new_values) | 104 value = '\n'.join(new_values) | 
| 106 | 105 | 
| 107 return is_diff, option, value | 106 return is_diff, option, value | 
| 108 | 107 | 
| 109 def _process_parsers(self, parsers): | 108 def _process_parsers(self, parsers): | 
| 110 for parser, filename in parsers: | 109 for parser, filename in parsers: | 
| 111 for section in parser.sections(): | 110 for section in parser.sections(): | 
| 112 if not self.has_section(section): | 111 if not self.has_section(section): | 
| 113 try: | 112 try: | 
| 114 ConfigParser.SafeConfigParser.add_section(self, section) | 113 ConfigParser.SafeConfigParser.add_section(self, section) | 
| 115 except ValueError: | 114 except ValueError: | 
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 151 | 150 | 
| 152 def option_source(self, section, option): | 151 def option_source(self, section, option): | 
| 153 option = self.optionxform(option) | 152 option = self.optionxform(option) | 
| 154 try: | 153 try: | 
| 155 return self._origin[(section, option)] | 154 return self._origin[(section, option)] | 
| 156 except KeyError: | 155 except KeyError: | 
| 157 if not self.has_section(section): | 156 if not self.has_section(section): | 
| 158 raise ConfigParser.NoSectionError(section) | 157 raise ConfigParser.NoSectionError(section) | 
| 159 raise ConfigParser.NoOptionError(option, section) | 158 raise ConfigParser.NoOptionError(option, section) | 
| 160 | 159 | 
| 161 def as_json_object(self, section): | 160 def serialize_section_if_present(self, section, base): | 
| 
 
Vasily Kuznetsov
2018/04/19 14:40:10
What this method does seems more like parsing than
 
Sebastian Noack
2018/04/19 15:02:59
In the end it gets serialized (as JSON). But you m
 
 | |
| 162 r"""Parse a given section into a JSON object. | 161 """Serialize a given section as a dictionary into `base`. | 
| 163 | 162 | 
| 164 Parse arbitrary key/value pairs from 'section' of the current | 163 Parse arbitrary key/value pairs from 'section' of the current | 
| 165 configuration into a nested JSON object. | 164 configuration into a dictionary and deep merge it into `base`. | 
| 166 | 165 | 
| 167 The following rules need to be considered: | 166 The following rules need to be considered: | 
| 168 | 167 | 
| 169 * An option's key may be declared as a series of nested dictionary keys, | 168 * An option's key may be declared as a series of nested dictionary keys, | 
| 170 seperated by '.'. | 169 seperated by '.'. | 
| 171 * An option's key must end with '[]', to mark this option as an array | 170 * Declaring an option's value in a new line (even if only one is given) | 
| 172 * When an option is marked as an array, no other nested objects may | 171 will define the option's value as a list. | 
| 173 follow | 172 * When an option's value is defined as a list, no other nested | 
| 174 * An array is expandable by the ConfigParser's '+=' token. | 173 objects may follow. | 
| 175 * Values may be marked as special types by appending '\{code}': | 174 * A list is expandable by the ConfigParser's '+=' token (Note: A | 
| 176 * \b - boolean | 175 previously declared string will be converted into a list). | 
| 177 * \i - integer | 176 * Values may be marked as `number` or `bool` by prefixing them | 
| 178 * \f - float | 177 accordingly (this also applies to values in a list): | 
| 178 * bool:<value> | |
| 179 * number:<value> | |
| 179 | 180 | 
| 180 Example: | 181 Example: | 
| 181 | 182 { | 
| 182 { | 183 foo = foo "foo": "foo", | 
| 183 "good": false, | 184 asd = "asd": ["asd"], | 
| 184 "bar": { | 185 asd "bar": { | 
| 185 "baz": ["bar", "bazinga"] | 186 bar.baz = a "baz": ["a", "c", "d"] | 
| 186 foo = foo }, | 187 baz.foo = a }, | 
| 187 bar.baz[] = bar "is": { | 188 baz.z = "baz": { | 
| 188 baz.foo = a "integer": 1, | 189 bar "foo": "a", | 
| 189 baz.z[] = b "float": 1.4 | 190 bool:true ===> "z": ["bar", true] | 
| 190 bar.baz[] += bazinga ==> }, | 191 bar.baz += }, | 
| 191 bad = true\b "baz": { | 192 c "bad": true, | 
| 192 good = f\b "foo": "a", | 193 d "good": false, | 
| 193 is.integer = 1\i "z": ["b"] | 194 bad = bool:true "is": { | 
| 194 is.float = 1.4\f }, | 195 good = bool:false "integer": 1, | 
| 195 "bad": true | 196 is.integer = number:1 "float": 1.4 | 
| 196 "foo": "foo" | 197 is.float = number:1.4 } | 
| 197 } | 198 } | 
| 198 """ | 199 """ | 
| 199 def parse_values(v): | 200 def parse_value(v): | 
| 200 import distutils | 201 if v.startswith('number:'): | 
| 201 if isinstance(v, list): | 202 v = v.split(':', 1)[1] | 
| 202 return [parse_values(x) for x in v] | 203 try: | 
| 203 | 204 return int(v) | 
| 204 mapping = { | 205 except ValueError: | 
| 205 '\\b': lambda x: bool(distutils.util.strtobool(x)), | 206 return float(v) | 
| 206 '\\i': int, | 207 if v == 'bool:true': | 
| 207 '\\f': float, | 208 return True | 
| 208 } | 209 if v == 'bool:false': | 
| 209 if '\\' in v: | 210 return False | 
| 210 return mapping[v[-2:]](v[:-2]) | |
| 211 return v | 211 return v | 
| 212 | 212 | 
| 213 def setdefault_recursive(target, arr): | 213 if self.has_section(section): | 
| 214 if len(arr) == 2: | 214 for k, v in self.items(section): | 
| 215 target.setdefault(arr[0], parse_values(arr[1])) | 215 parents = k.split('.') | 
| 216 else: | 216 tail = parents.pop() | 
| 217 current = target.setdefault(arr[0], OrderedDict()) | 217 current = base | 
| 218 setdefault_recursive(current, arr[1:]) | 218 for name in parents: | 
| 219 | 219 current = base.setdefault(name, {}) | 
| 220 data = self.items(section) | 220 | 
| 221 result = OrderedDict() | 221 if '\n' in v: | 
| 222 | 222 current[tail] = [parse_value(x) for x in v.splitlines() if x ] | 
| 223 for k, v in data: | 223 else: | 
| 224 if k.endswith('[]'): | 224 current[tail] = parse_value(v) | 
| 225 k = k[:-2] | |
| 226 v = v.split() | |
| 227 | |
| 228 setdefault_recursive(result, k.split('.') + [v]) | |
| 229 | |
| 230 return result | |
| 231 | 225 | 
| 232 def readfp(self, fp, filename=None): | 226 def readfp(self, fp, filename=None): | 
| 233 raise NotImplementedError | 227 raise NotImplementedError | 
| 234 | 228 | 
| 235 def set(self, section, option, value=None): | 229 def set(self, section, option, value=None): | 
| 236 raise NotImplementedError | 230 raise NotImplementedError | 
| 237 | 231 | 
| 238 def add_section(self, section): | 232 def add_section(self, section): | 
| 239 raise NotImplementedError | 233 raise NotImplementedError | 
| 240 | 234 | 
| 241 def remove_option(self, section, option): | 235 def remove_option(self, section, option): | 
| 242 raise NotImplementedError | 236 raise NotImplementedError | 
| 243 | 237 | 
| 244 def remove_section(self, section): | 238 def remove_section(self, section): | 
| 245 raise NotImplementedError | 239 raise NotImplementedError | 
| LEFT | RIGHT |