2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt |
2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt |
3 |
3 |
4 """Config file for coverage.py""" |
4 """Config file for coverage.py""" |
5 |
5 |
6 import collections |
6 import collections |
|
7 import configparser |
7 import copy |
8 import copy |
8 import os |
9 import os |
9 import os.path |
10 import os.path |
10 import re |
11 import re |
11 |
12 |
12 from coverage import env |
13 from coverage.exceptions import CoverageException |
13 from coverage.backward import configparser, iitems, string_class |
14 from coverage.misc import contract, isolate_module, substitute_variables |
14 from coverage.misc import contract, CoverageException, isolate_module |
|
15 from coverage.misc import substitute_variables |
|
16 |
15 |
17 from coverage.tomlconfig import TomlConfigParser, TomlDecodeError |
16 from coverage.tomlconfig import TomlConfigParser, TomlDecodeError |
18 |
17 |
19 os = isolate_module(os) |
18 os = isolate_module(os) |
20 |
19 |
33 configparser.RawConfigParser.__init__(self) |
32 configparser.RawConfigParser.__init__(self) |
34 self.section_prefixes = ["coverage:"] |
33 self.section_prefixes = ["coverage:"] |
35 if our_file: |
34 if our_file: |
36 self.section_prefixes.append("") |
35 self.section_prefixes.append("") |
37 |
36 |
38 def read(self, filenames, encoding=None): |
37 def read(self, filenames, encoding_unused=None): |
39 """Read a file name as UTF-8 configuration data.""" |
38 """Read a file name as UTF-8 configuration data.""" |
40 kwargs = {} |
39 return configparser.RawConfigParser.read(self, filenames, encoding="utf-8") |
41 if env.PYVERSION >= (3, 2): |
|
42 kwargs['encoding'] = encoding or "utf-8" |
|
43 return configparser.RawConfigParser.read(self, filenames, **kwargs) |
|
44 |
40 |
45 def has_option(self, section, option): |
41 def has_option(self, section, option): |
46 for section_prefix in self.section_prefixes: |
42 for section_prefix in self.section_prefixes: |
47 real_section = section_prefix + section |
43 real_section = section_prefix + section |
48 has = configparser.RawConfigParser.has_option(self, real_section, option) |
44 has = configparser.RawConfigParser.has_option(self, real_section, option) |
126 value = value.strip() |
122 value = value.strip() |
127 try: |
123 try: |
128 re.compile(value) |
124 re.compile(value) |
129 except re.error as e: |
125 except re.error as e: |
130 raise CoverageException( |
126 raise CoverageException( |
131 "Invalid [%s].%s value %r: %s" % (section, option, value, e) |
127 f"Invalid [{section}].{option} value {value!r}: {e}" |
132 ) |
128 ) from e |
133 if value: |
129 if value: |
134 value_list.append(value) |
130 value_list.append(value) |
135 return value_list |
131 return value_list |
136 |
132 |
137 |
133 |
152 'while (True|1|False|0):', |
148 'while (True|1|False|0):', |
153 'if (True|1|False|0):', |
149 'if (True|1|False|0):', |
154 ] |
150 ] |
155 |
151 |
156 |
152 |
157 class CoverageConfig(object): |
153 class CoverageConfig: |
158 """Coverage.py configuration. |
154 """Coverage.py configuration. |
159 |
155 |
160 The attributes of this class are the various settings that control the |
156 The attributes of this class are the various settings that control the |
161 operation of coverage.py. |
157 operation of coverage.py. |
162 |
158 |
243 "run_omit", "run_include", |
239 "run_omit", "run_include", |
244 ] |
240 ] |
245 |
241 |
246 def from_args(self, **kwargs): |
242 def from_args(self, **kwargs): |
247 """Read config values from `kwargs`.""" |
243 """Read config values from `kwargs`.""" |
248 for k, v in iitems(kwargs): |
244 for k, v in kwargs.items(): |
249 if v is not None: |
245 if v is not None: |
250 if k in self.MUST_BE_LIST and isinstance(v, string_class): |
246 if k in self.MUST_BE_LIST and isinstance(v, str): |
251 v = [v] |
247 v = [v] |
252 setattr(self, k, v) |
248 setattr(self, k, v) |
253 |
249 |
254 @contract(filename=str) |
250 @contract(filename=str) |
255 def from_file(self, filename, our_file): |
251 def from_file(self, filename, warn, our_file): |
256 """Read configuration from a .rc file. |
252 """Read configuration from a .rc file. |
257 |
253 |
258 `filename` is a file name to read. |
254 `filename` is a file name to read. |
259 |
255 |
260 `our_file` is True if this config file is specifically for coverage, |
256 `our_file` is True if this config file is specifically for coverage, |
274 self.attempted_config_files.append(filename) |
270 self.attempted_config_files.append(filename) |
275 |
271 |
276 try: |
272 try: |
277 files_read = cp.read(filename) |
273 files_read = cp.read(filename) |
278 except (configparser.Error, TomlDecodeError) as err: |
274 except (configparser.Error, TomlDecodeError) as err: |
279 raise CoverageException("Couldn't read config file %s: %s" % (filename, err)) |
275 raise CoverageException(f"Couldn't read config file {filename}: {err}") from err |
280 if not files_read: |
276 if not files_read: |
281 return False |
277 return False |
282 |
278 |
283 self.config_files_read.extend(map(os.path.abspath, files_read)) |
279 self.config_files_read.extend(map(os.path.abspath, files_read)) |
284 |
280 |
287 for option_spec in self.CONFIG_FILE_OPTIONS: |
283 for option_spec in self.CONFIG_FILE_OPTIONS: |
288 was_set = self._set_attr_from_config_option(cp, *option_spec) |
284 was_set = self._set_attr_from_config_option(cp, *option_spec) |
289 if was_set: |
285 if was_set: |
290 any_set = True |
286 any_set = True |
291 except ValueError as err: |
287 except ValueError as err: |
292 raise CoverageException("Couldn't read config file %s: %s" % (filename, err)) |
288 raise CoverageException(f"Couldn't read config file {filename}: {err}") from err |
293 |
289 |
294 # Check that there are no unrecognized options. |
290 # Check that there are no unrecognized options. |
295 all_options = collections.defaultdict(set) |
291 all_options = collections.defaultdict(set) |
296 for option_spec in self.CONFIG_FILE_OPTIONS: |
292 for option_spec in self.CONFIG_FILE_OPTIONS: |
297 section, option = option_spec[1].split(":") |
293 section, option = option_spec[1].split(":") |
298 all_options[section].add(option) |
294 all_options[section].add(option) |
299 |
295 |
300 for section, options in iitems(all_options): |
296 for section, options in all_options.items(): |
301 real_section = cp.has_section(section) |
297 real_section = cp.has_section(section) |
302 if real_section: |
298 if real_section: |
303 for unknown in set(cp.options(section)) - options: |
299 for unknown in set(cp.options(section)) - options: |
304 raise CoverageException( |
300 warn( |
305 "Unrecognized option '[%s] %s=' in config file %s" % ( |
301 "Unrecognized option '[{}] {}=' in config file {}".format( |
306 real_section, unknown, filename |
302 real_section, unknown, filename |
307 ) |
303 ) |
308 ) |
304 ) |
309 |
305 |
310 # [paths] is special |
306 # [paths] is special |
445 if key and plugin_name in self.plugins: |
441 if key and plugin_name in self.plugins: |
446 self.plugin_options.setdefault(plugin_name, {})[key] = value |
442 self.plugin_options.setdefault(plugin_name, {})[key] = value |
447 return |
443 return |
448 |
444 |
449 # If we get here, we didn't find the option. |
445 # If we get here, we didn't find the option. |
450 raise CoverageException("No such option: %r" % option_name) |
446 raise CoverageException(f"No such option: {option_name!r}") |
451 |
447 |
452 def get_option(self, option_name): |
448 def get_option(self, option_name): |
453 """Get an option from the configuration. |
449 """Get an option from the configuration. |
454 |
450 |
455 `option_name` is a colon-separated string indicating the section and |
451 `option_name` is a colon-separated string indicating the section and |
473 plugin_name, _, key = option_name.partition(":") |
469 plugin_name, _, key = option_name.partition(":") |
474 if key and plugin_name in self.plugins: |
470 if key and plugin_name in self.plugins: |
475 return self.plugin_options.get(plugin_name, {}).get(key) |
471 return self.plugin_options.get(plugin_name, {}).get(key) |
476 |
472 |
477 # If we get here, we didn't find the option. |
473 # If we get here, we didn't find the option. |
478 raise CoverageException("No such option: %r" % option_name) |
474 raise CoverageException(f"No such option: {option_name!r}") |
479 |
475 |
480 def post_process_file(self, path): |
476 def post_process_file(self, path): |
481 """Make final adjustments to a file path to make it usable.""" |
477 """Make final adjustments to a file path to make it usable.""" |
482 return os.path.expanduser(path) |
478 return os.path.expanduser(path) |
483 |
479 |
519 ("pyproject.toml", False, False), |
515 ("pyproject.toml", False, False), |
520 ] |
516 ] |
521 return files_to_try |
517 return files_to_try |
522 |
518 |
523 |
519 |
524 def read_coverage_config(config_file, **kwargs): |
520 def read_coverage_config(config_file, warn, **kwargs): |
525 """Read the coverage.py configuration. |
521 """Read the coverage.py configuration. |
526 |
522 |
527 Arguments: |
523 Arguments: |
528 config_file: a boolean or string, see the `Coverage` class for the |
524 config_file: a boolean or string, see the `Coverage` class for the |
529 tricky details. |
525 tricky details. |
|
526 warn: a function to issue warnings. |
530 all others: keyword arguments from the `Coverage` class, used for |
527 all others: keyword arguments from the `Coverage` class, used for |
531 setting values in the configuration. |
528 setting values in the configuration. |
532 |
529 |
533 Returns: |
530 Returns: |
534 config: |
531 config: |
543 # 2) from a file: |
540 # 2) from a file: |
544 if config_file: |
541 if config_file: |
545 files_to_try = config_files_to_try(config_file) |
542 files_to_try = config_files_to_try(config_file) |
546 |
543 |
547 for fname, our_file, specified_file in files_to_try: |
544 for fname, our_file, specified_file in files_to_try: |
548 config_read = config.from_file(fname, our_file=our_file) |
545 config_read = config.from_file(fname, warn, our_file=our_file) |
549 if config_read: |
546 if config_read: |
550 break |
547 break |
551 if specified_file: |
548 if specified_file: |
552 raise CoverageException("Couldn't read '%s' as a config file" % fname) |
549 raise CoverageException(f"Couldn't read {fname!r} as a config file") |
553 |
550 |
554 # $set_env.py: COVERAGE_DEBUG - Options for --debug. |
551 # $set_env.py: COVERAGE_DEBUG - Options for --debug. |
555 # 3) from environment variables: |
552 # 3) from environment variables: |
556 env_data_file = os.environ.get('COVERAGE_FILE') |
553 env_data_file = os.environ.get('COVERAGE_FILE') |
557 if env_data_file: |
554 if env_data_file: |