|
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
|
2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
|
3 |
1 """Config file for coverage.py""" |
4 """Config file for coverage.py""" |
2 |
5 |
3 import os, re, sys |
6 import collections |
4 from .backward import string_class, iitems |
7 import os |
5 |
8 import re |
6 # In py3, # ConfigParser was renamed to the more-standard configparser |
9 import sys |
7 try: |
10 |
8 import configparser # pylint: disable=F0401 |
11 from coverage.backward import configparser, iitems, string_class |
9 except ImportError: |
12 from coverage.misc import CoverageException |
10 import ConfigParser as configparser |
|
11 |
13 |
12 |
14 |
13 class HandyConfigParser(configparser.RawConfigParser): |
15 class HandyConfigParser(configparser.RawConfigParser): |
14 """Our specialization of ConfigParser.""" |
16 """Our specialization of ConfigParser.""" |
15 |
17 |
|
18 def __init__(self, section_prefix): |
|
19 configparser.RawConfigParser.__init__(self) |
|
20 self.section_prefix = section_prefix |
|
21 |
16 def read(self, filename): |
22 def read(self, filename): |
17 """Read a filename as UTF-8 configuration data.""" |
23 """Read a file name as UTF-8 configuration data.""" |
18 kwargs = {} |
24 kwargs = {} |
19 if sys.version_info >= (3, 2): |
25 if sys.version_info >= (3, 2): |
20 kwargs['encoding'] = "utf-8" |
26 kwargs['encoding'] = "utf-8" |
21 return configparser.RawConfigParser.read(self, filename, **kwargs) |
27 return configparser.RawConfigParser.read(self, filename, **kwargs) |
22 |
28 |
23 def get(self, *args, **kwargs): |
29 def has_option(self, section, option): |
24 v = configparser.RawConfigParser.get(self, *args, **kwargs) |
30 section = self.section_prefix + section |
|
31 return configparser.RawConfigParser.has_option(self, section, option) |
|
32 |
|
33 def has_section(self, section): |
|
34 section = self.section_prefix + section |
|
35 return configparser.RawConfigParser.has_section(self, section) |
|
36 |
|
37 def options(self, section): |
|
38 section = self.section_prefix + section |
|
39 return configparser.RawConfigParser.options(self, section) |
|
40 |
|
41 def get_section(self, section): |
|
42 """Get the contents of a section, as a dictionary.""" |
|
43 d = {} |
|
44 for opt in self.options(section): |
|
45 d[opt] = self.get(section, opt) |
|
46 return d |
|
47 |
|
48 def get(self, section, *args, **kwargs): |
|
49 """Get a value, replacing environment variables also. |
|
50 |
|
51 The arguments are the same as `RawConfigParser.get`, but in the found |
|
52 value, ``$WORD`` or ``${WORD}`` are replaced by the value of the |
|
53 environment variable ``WORD``. |
|
54 |
|
55 Returns the finished value. |
|
56 |
|
57 """ |
|
58 section = self.section_prefix + section |
|
59 v = configparser.RawConfigParser.get(self, section, *args, **kwargs) |
25 def dollar_replace(m): |
60 def dollar_replace(m): |
26 """Called for each $replacement.""" |
61 """Called for each $replacement.""" |
27 # Only one of the groups will have matched, just get its text. |
62 # Only one of the groups will have matched, just get its text. |
28 word = [w for w in m.groups() if w is not None][0] |
63 word = next(w for w in m.groups() if w is not None) # pragma: part covered |
29 if word == "$": |
64 if word == "$": |
30 return "$" |
65 return "$" |
31 else: |
66 else: |
32 return os.environ.get(word, '') |
67 return os.environ.get(word, '') |
33 |
68 |
104 self.attempted_config_files = [] |
150 self.attempted_config_files = [] |
105 self.config_files = [] |
151 self.config_files = [] |
106 |
152 |
107 # Defaults for [run] |
153 # Defaults for [run] |
108 self.branch = False |
154 self.branch = False |
|
155 self.concurrency = None |
109 self.cover_pylib = False |
156 self.cover_pylib = False |
110 self.data_file = ".coverage" |
157 self.data_file = ".coverage" |
|
158 self.debug = [] |
|
159 self.note = None |
111 self.parallel = False |
160 self.parallel = False |
|
161 self.plugins = [] |
|
162 self.source = None |
112 self.timid = False |
163 self.timid = False |
113 self.source = None |
|
114 self.debug = [] |
|
115 |
164 |
116 # Defaults for [report] |
165 # Defaults for [report] |
117 self.exclude_list = DEFAULT_EXCLUDE[:] |
166 self.exclude_list = DEFAULT_EXCLUDE[:] |
|
167 self.fail_under = 0 |
118 self.ignore_errors = False |
168 self.ignore_errors = False |
119 self.include = None |
169 self.include = None |
120 self.omit = None |
170 self.omit = None |
|
171 self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:] |
121 self.partial_list = DEFAULT_PARTIAL[:] |
172 self.partial_list = DEFAULT_PARTIAL[:] |
122 self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:] |
|
123 self.precision = 0 |
173 self.precision = 0 |
124 self.show_missing = False |
174 self.show_missing = False |
|
175 self.skip_covered = False |
125 |
176 |
126 # Defaults for [html] |
177 # Defaults for [html] |
|
178 self.extra_css = None |
127 self.html_dir = "htmlcov" |
179 self.html_dir = "htmlcov" |
128 self.extra_css = None |
|
129 self.html_title = "Coverage report" |
180 self.html_title = "Coverage report" |
130 |
181 |
131 # Defaults for [xml] |
182 # Defaults for [xml] |
132 self.xml_output = "coverage.xml" |
183 self.xml_output = "coverage.xml" |
|
184 self.xml_package_depth = 99 |
133 |
185 |
134 # Defaults for [paths] |
186 # Defaults for [paths] |
135 self.paths = {} |
187 self.paths = {} |
136 |
188 |
137 def from_environment(self, env_var): |
189 # Options for plugins |
138 """Read configuration from the `env_var` environment variable.""" |
190 self.plugin_options = {} |
139 # Timidity: for nose users, read an environment variable. This is a |
191 |
140 # cheap hack, since the rest of the command line arguments aren't |
192 MUST_BE_LIST = ["omit", "include", "debug", "plugins"] |
141 # recognized, but it solves some users' problems. |
|
142 env = os.environ.get(env_var, '') |
|
143 if env: |
|
144 self.timid = ('--timid' in env) |
|
145 |
|
146 MUST_BE_LIST = ["omit", "include", "debug"] |
|
147 |
193 |
148 def from_args(self, **kwargs): |
194 def from_args(self, **kwargs): |
149 """Read config values from `kwargs`.""" |
195 """Read config values from `kwargs`.""" |
150 for k, v in iitems(kwargs): |
196 for k, v in iitems(kwargs): |
151 if v is not None: |
197 if v is not None: |
152 if k in self.MUST_BE_LIST and isinstance(v, string_class): |
198 if k in self.MUST_BE_LIST and isinstance(v, string_class): |
153 v = [v] |
199 v = [v] |
154 setattr(self, k, v) |
200 setattr(self, k, v) |
155 |
201 |
156 def from_file(self, filename): |
202 def from_file(self, filename, section_prefix=""): |
157 """Read configuration from a .rc file. |
203 """Read configuration from a .rc file. |
158 |
204 |
159 `filename` is a file name to read. |
205 `filename` is a file name to read. |
160 |
206 |
|
207 Returns True or False, whether the file could be read. |
|
208 |
161 """ |
209 """ |
162 self.attempted_config_files.append(filename) |
210 self.attempted_config_files.append(filename) |
163 |
211 |
164 cp = HandyConfigParser() |
212 cp = HandyConfigParser(section_prefix) |
165 files_read = cp.read(filename) |
213 try: |
166 if files_read is not None: # return value changed in 2.4 |
214 files_read = cp.read(filename) |
167 self.config_files.extend(files_read) |
215 except configparser.Error as err: |
168 |
216 raise CoverageException("Couldn't read config file %s: %s" % (filename, err)) |
|
217 if not files_read: |
|
218 return False |
|
219 |
|
220 self.config_files.extend(files_read) |
|
221 |
|
222 try: |
|
223 for option_spec in self.CONFIG_FILE_OPTIONS: |
|
224 self._set_attr_from_config_option(cp, *option_spec) |
|
225 except ValueError as err: |
|
226 raise CoverageException("Couldn't read config file %s: %s" % (filename, err)) |
|
227 |
|
228 # Check that there are no unrecognized options. |
|
229 all_options = collections.defaultdict(set) |
169 for option_spec in self.CONFIG_FILE_OPTIONS: |
230 for option_spec in self.CONFIG_FILE_OPTIONS: |
170 self.set_attr_from_config_option(cp, *option_spec) |
231 section, option = option_spec[1].split(":") |
|
232 all_options[section].add(option) |
|
233 |
|
234 for section, options in iitems(all_options): |
|
235 if cp.has_section(section): |
|
236 for unknown in set(cp.options(section)) - options: |
|
237 if section_prefix: |
|
238 section = section_prefix + section |
|
239 raise CoverageException( |
|
240 "Unrecognized option '[%s] %s=' in config file %s" % ( |
|
241 section, unknown, filename |
|
242 ) |
|
243 ) |
171 |
244 |
172 # [paths] is special |
245 # [paths] is special |
173 if cp.has_section('paths'): |
246 if cp.has_section('paths'): |
174 for option in cp.options('paths'): |
247 for option in cp.options('paths'): |
175 self.paths[option] = cp.getlist('paths', option) |
248 self.paths[option] = cp.getlist('paths', option) |
176 |
249 |
|
250 # plugins can have options |
|
251 for plugin in self.plugins: |
|
252 if cp.has_section(plugin): |
|
253 self.plugin_options[plugin] = cp.get_section(plugin) |
|
254 |
|
255 return True |
|
256 |
177 CONFIG_FILE_OPTIONS = [ |
257 CONFIG_FILE_OPTIONS = [ |
|
258 # These are *args for _set_attr_from_config_option: |
|
259 # (attr, where, type_="") |
|
260 # |
|
261 # attr is the attribute to set on the CoverageConfig object. |
|
262 # where is the section:name to read from the configuration file. |
|
263 # type_ is the optional type to apply, by using .getTYPE to read the |
|
264 # configuration value from the file. |
|
265 |
178 # [run] |
266 # [run] |
179 ('branch', 'run:branch', 'boolean'), |
267 ('branch', 'run:branch', 'boolean'), |
|
268 ('concurrency', 'run:concurrency'), |
180 ('cover_pylib', 'run:cover_pylib', 'boolean'), |
269 ('cover_pylib', 'run:cover_pylib', 'boolean'), |
181 ('data_file', 'run:data_file'), |
270 ('data_file', 'run:data_file'), |
182 ('debug', 'run:debug', 'list'), |
271 ('debug', 'run:debug', 'list'), |
183 ('include', 'run:include', 'list'), |
272 ('include', 'run:include', 'list'), |
|
273 ('note', 'run:note'), |
184 ('omit', 'run:omit', 'list'), |
274 ('omit', 'run:omit', 'list'), |
185 ('parallel', 'run:parallel', 'boolean'), |
275 ('parallel', 'run:parallel', 'boolean'), |
|
276 ('plugins', 'run:plugins', 'list'), |
186 ('source', 'run:source', 'list'), |
277 ('source', 'run:source', 'list'), |
187 ('timid', 'run:timid', 'boolean'), |
278 ('timid', 'run:timid', 'boolean'), |
188 |
279 |
189 # [report] |
280 # [report] |
190 ('exclude_list', 'report:exclude_lines', 'linelist'), |
281 ('exclude_list', 'report:exclude_lines', 'regexlist'), |
|
282 ('fail_under', 'report:fail_under', 'int'), |
191 ('ignore_errors', 'report:ignore_errors', 'boolean'), |
283 ('ignore_errors', 'report:ignore_errors', 'boolean'), |
192 ('include', 'report:include', 'list'), |
284 ('include', 'report:include', 'list'), |
193 ('omit', 'report:omit', 'list'), |
285 ('omit', 'report:omit', 'list'), |
194 ('partial_list', 'report:partial_branches', 'linelist'), |
286 ('partial_always_list', 'report:partial_branches_always', 'regexlist'), |
195 ('partial_always_list', 'report:partial_branches_always', 'linelist'), |
287 ('partial_list', 'report:partial_branches', 'regexlist'), |
196 ('precision', 'report:precision', 'int'), |
288 ('precision', 'report:precision', 'int'), |
197 ('show_missing', 'report:show_missing', 'boolean'), |
289 ('show_missing', 'report:show_missing', 'boolean'), |
|
290 ('skip_covered', 'report:skip_covered', 'boolean'), |
198 |
291 |
199 # [html] |
292 # [html] |
|
293 ('extra_css', 'html:extra_css'), |
200 ('html_dir', 'html:directory'), |
294 ('html_dir', 'html:directory'), |
201 ('extra_css', 'html:extra_css'), |
|
202 ('html_title', 'html:title'), |
295 ('html_title', 'html:title'), |
203 |
296 |
204 # [xml] |
297 # [xml] |
205 ('xml_output', 'xml:output'), |
298 ('xml_output', 'xml:output'), |
206 ] |
299 ('xml_package_depth', 'xml:package_depth', 'int'), |
207 |
300 ] |
208 def set_attr_from_config_option(self, cp, attr, where, type_=''): |
301 |
|
302 def _set_attr_from_config_option(self, cp, attr, where, type_=''): |
209 """Set an attribute on self if it exists in the ConfigParser.""" |
303 """Set an attribute on self if it exists in the ConfigParser.""" |
210 section, option = where.split(":") |
304 section, option = where.split(":") |
211 if cp.has_option(section, option): |
305 if cp.has_option(section, option): |
212 method = getattr(cp, 'get'+type_) |
306 method = getattr(cp, 'get' + type_) |
213 setattr(self, attr, method(section, option)) |
307 setattr(self, attr, method(section, option)) |
|
308 |
|
309 def get_plugin_options(self, plugin): |
|
310 """Get a dictionary of options for the plugin named `plugin`.""" |
|
311 return self.plugin_options.get(plugin, {}) |
|
312 |
|
313 def set_option(self, option_name, value): |
|
314 """Set an option in the configuration. |
|
315 |
|
316 `option_name` is a colon-separated string indicating the section and |
|
317 option name. For example, the ``branch`` option in the ``[run]`` |
|
318 section of the config file would be indicated with `"run:branch"`. |
|
319 |
|
320 `value` is the new value for the option. |
|
321 |
|
322 """ |
|
323 |
|
324 # Check all the hard-coded options. |
|
325 for option_spec in self.CONFIG_FILE_OPTIONS: |
|
326 attr, where = option_spec[:2] |
|
327 if where == option_name: |
|
328 setattr(self, attr, value) |
|
329 return |
|
330 |
|
331 # See if it's a plugin option. |
|
332 plugin_name, _, key = option_name.partition(":") |
|
333 if key and plugin_name in self.plugins: |
|
334 self.plugin_options.setdefault(plugin_name, {})[key] = value |
|
335 return |
|
336 |
|
337 # If we get here, we didn't find the option. |
|
338 raise CoverageException("No such option: %r" % option_name) |
|
339 |
|
340 def get_option(self, option_name): |
|
341 """Get an option from the configuration. |
|
342 |
|
343 `option_name` is a colon-separated string indicating the section and |
|
344 option name. For example, the ``branch`` option in the ``[run]`` |
|
345 section of the config file would be indicated with `"run:branch"`. |
|
346 |
|
347 Returns the value of the option. |
|
348 |
|
349 """ |
|
350 |
|
351 # Check all the hard-coded options. |
|
352 for option_spec in self.CONFIG_FILE_OPTIONS: |
|
353 attr, where = option_spec[:2] |
|
354 if where == option_name: |
|
355 return getattr(self, attr) |
|
356 |
|
357 # See if it's a plugin option. |
|
358 plugin_name, _, key = option_name.partition(":") |
|
359 if key and plugin_name in self.plugins: |
|
360 return self.plugin_options.get(plugin_name, {}).get(key) |
|
361 |
|
362 # If we get here, we didn't find the option. |
|
363 raise CoverageException("No such option: %r" % option_name) |