Left: | ||
Right: |
LEFT | RIGHT |
---|---|
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 Loading... | |
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 |
LEFT | RIGHT |