eric6/DebugClients/Python/coverage/config.py

changeset 7427
362cd1b6f81a
parent 6942
2602857055c5
child 7702
f8b97639deb5
equal deleted inserted replaced
7426:dc171b1d8261 7427:362cd1b6f81a
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 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 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 copy
7 import os 8 import os
9 import os.path
8 import re 10 import re
9 import sys 11
10 12 from coverage import env
11 from coverage.backward import configparser, iitems, string_class 13 from coverage.backward import configparser, iitems, string_class
12 from coverage.misc import contract, CoverageException, isolate_module 14 from coverage.misc import contract, CoverageException, isolate_module
15 from coverage.misc import substitute_variables
16
17 from coverage.tomlconfig import TomlConfigParser, TomlDecodeError
13 18
14 os = isolate_module(os) 19 os = isolate_module(os)
15 20
16 21
17 class HandyConfigParser(configparser.RawConfigParser): 22 class HandyConfigParser(configparser.RawConfigParser):
28 configparser.RawConfigParser.__init__(self) 33 configparser.RawConfigParser.__init__(self)
29 self.section_prefixes = ["coverage:"] 34 self.section_prefixes = ["coverage:"]
30 if our_file: 35 if our_file:
31 self.section_prefixes.append("") 36 self.section_prefixes.append("")
32 37
33 def read(self, filenames): 38 def read(self, filenames, encoding=None):
34 """Read a file name as UTF-8 configuration data.""" 39 """Read a file name as UTF-8 configuration data."""
35 kwargs = {} 40 kwargs = {}
36 if sys.version_info >= (3, 2): 41 if env.PYVERSION >= (3, 2):
37 kwargs['encoding'] = "utf-8" 42 kwargs['encoding'] = encoding or "utf-8"
38 return configparser.RawConfigParser.read(self, filenames, **kwargs) 43 return configparser.RawConfigParser.read(self, filenames, **kwargs)
39 44
40 def has_option(self, section, option): 45 def has_option(self, section, option):
41 for section_prefix in self.section_prefixes: 46 for section_prefix in self.section_prefixes:
42 real_section = section_prefix + section 47 real_section = section_prefix + section
83 break 88 break
84 else: 89 else:
85 raise configparser.NoOptionError 90 raise configparser.NoOptionError
86 91
87 v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs) 92 v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs)
88 def dollar_replace(m): 93 v = substitute_variables(v, os.environ)
89 """Called for each $replacement."""
90 # Only one of the groups will have matched, just get its text.
91 word = next(w for w in m.groups() if w is not None) # pragma: part covered
92 if word == "$":
93 return "$"
94 else:
95 return os.environ.get(word, '')
96
97 dollar_pattern = r"""(?x) # Use extended regex syntax
98 \$(?: # A dollar sign, then
99 (?P<v1>\w+) | # a plain word,
100 {(?P<v2>\w+)} | # or a {-wrapped word,
101 (?P<char>[$]) # or a dollar sign.
102 )
103 """
104 v = re.sub(dollar_pattern, dollar_replace, v)
105 return v 94 return v
106 95
107 def getlist(self, section, option): 96 def getlist(self, section, option):
108 """Read a list of strings. 97 """Read a list of strings.
109 98
170 159
171 The attributes of this class are the various settings that control the 160 The attributes of this class are the various settings that control the
172 operation of coverage.py. 161 operation of coverage.py.
173 162
174 """ 163 """
164 # pylint: disable=too-many-instance-attributes
165
175 def __init__(self): 166 def __init__(self):
176 """Initialize the configuration attributes to their defaults.""" 167 """Initialize the configuration attributes to their defaults."""
177 # Metadata about the config. 168 # Metadata about the config.
169 # We tried to read these config files.
178 self.attempted_config_files = [] 170 self.attempted_config_files = []
179 self.config_files = [] 171 # We did read these config files, but maybe didn't find any content for us.
172 self.config_files_read = []
173 # The file that gave us our configuration.
174 self.config_file = None
175 self._config_contents = None
180 176
181 # Defaults for [run] and [report] 177 # Defaults for [run] and [report]
182 self._include = None 178 self._include = None
183 self._omit = None 179 self._omit = None
184 180
185 # Defaults for [run] 181 # Defaults for [run]
186 self.branch = False 182 self.branch = False
183 self.command_line = None
187 self.concurrency = None 184 self.concurrency = None
185 self.context = None
188 self.cover_pylib = False 186 self.cover_pylib = False
189 self.data_file = ".coverage" 187 self.data_file = ".coverage"
190 self.debug = [] 188 self.debug = []
191 self.disable_warnings = [] 189 self.disable_warnings = []
190 self.dynamic_context = None
192 self.note = None 191 self.note = None
193 self.parallel = False 192 self.parallel = False
194 self.plugins = [] 193 self.plugins = []
195 self.source = None 194 self.relative_files = False
196 self.run_include = None 195 self.run_include = None
197 self.run_omit = None 196 self.run_omit = None
197 self.source = None
198 self.timid = False 198 self.timid = False
199 self._crash = None
199 200
200 # Defaults for [report] 201 # Defaults for [report]
201 self.exclude_list = DEFAULT_EXCLUDE[:] 202 self.exclude_list = DEFAULT_EXCLUDE[:]
202 self.fail_under = 0.0 203 self.fail_under = 0.0
203 self.ignore_errors = False 204 self.ignore_errors = False
204 self.report_include = None 205 self.report_include = None
205 self.report_omit = None 206 self.report_omit = None
206 self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:] 207 self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
207 self.partial_list = DEFAULT_PARTIAL[:] 208 self.partial_list = DEFAULT_PARTIAL[:]
208 self.precision = 0 209 self.precision = 0
210 self.report_contexts = None
209 self.show_missing = False 211 self.show_missing = False
210 self.skip_covered = False 212 self.skip_covered = False
213 self.skip_empty = False
211 214
212 # Defaults for [html] 215 # Defaults for [html]
213 self.extra_css = None 216 self.extra_css = None
214 self.html_dir = "htmlcov" 217 self.html_dir = "htmlcov"
215 self.html_title = "Coverage report" 218 self.html_title = "Coverage report"
219 self.show_contexts = False
216 220
217 # Defaults for [xml] 221 # Defaults for [xml]
218 self.xml_output = "coverage.xml" 222 self.xml_output = "coverage.xml"
219 self.xml_package_depth = 99 223 self.xml_package_depth = 99
220 224
225 # Defaults for [json]
226 self.json_output = "coverage.json"
227 self.json_pretty_print = False
228 self.json_show_contexts = False
229
221 # Defaults for [paths] 230 # Defaults for [paths]
222 self.paths = {} 231 self.paths = collections.OrderedDict()
223 232
224 # Options for plugins 233 # Options for plugins
225 self.plugin_options = {} 234 self.plugin_options = {}
226 235
227 MUST_BE_LIST = [ 236 MUST_BE_LIST = [
250 259
251 Returns True or False, whether the file could be read, and it had some 260 Returns True or False, whether the file could be read, and it had some
252 coverage.py settings in it. 261 coverage.py settings in it.
253 262
254 """ 263 """
264 _, ext = os.path.splitext(filename)
265 if ext == '.toml':
266 cp = TomlConfigParser(our_file)
267 else:
268 cp = HandyConfigParser(our_file)
269
255 self.attempted_config_files.append(filename) 270 self.attempted_config_files.append(filename)
256 271
257 cp = HandyConfigParser(our_file)
258 try: 272 try:
259 files_read = cp.read(filename) 273 files_read = cp.read(filename)
260 except configparser.Error as err: 274 except (configparser.Error, TomlDecodeError) as err:
261 raise CoverageException("Couldn't read config file %s: %s" % (filename, err)) 275 raise CoverageException("Couldn't read config file %s: %s" % (filename, err))
262 if not files_read: 276 if not files_read:
263 return False 277 return False
264 278
265 self.config_files.extend(files_read) 279 self.config_files_read.extend(map(os.path.abspath, files_read))
266 280
267 any_set = False 281 any_set = False
268 try: 282 try:
269 for option_spec in self.CONFIG_FILE_OPTIONS: 283 for option_spec in self.CONFIG_FILE_OPTIONS:
270 was_set = self._set_attr_from_config_option(cp, *option_spec) 284 was_set = self._set_attr_from_config_option(cp, *option_spec)
303 317
304 # Was this file used as a config file? If it's specifically our file, 318 # Was this file used as a config file? If it's specifically our file,
305 # then it was used. If we're piggybacking on someone else's file, 319 # then it was used. If we're piggybacking on someone else's file,
306 # then it was only used if we found some settings in it. 320 # then it was only used if we found some settings in it.
307 if our_file: 321 if our_file:
308 return True 322 used = True
309 else: 323 else:
310 return any_set 324 used = any_set
325
326 if used:
327 self.config_file = os.path.abspath(filename)
328 with open(filename) as f:
329 self._config_contents = f.read()
330
331 return used
332
333 def copy(self):
334 """Return a copy of the configuration."""
335 return copy.deepcopy(self)
311 336
312 CONFIG_FILE_OPTIONS = [ 337 CONFIG_FILE_OPTIONS = [
313 # These are *args for _set_attr_from_config_option: 338 # These are *args for _set_attr_from_config_option:
314 # (attr, where, type_="") 339 # (attr, where, type_="")
315 # 340 #
318 # type_ is the optional type to apply, by using .getTYPE to read the 343 # type_ is the optional type to apply, by using .getTYPE to read the
319 # configuration value from the file. 344 # configuration value from the file.
320 345
321 # [run] 346 # [run]
322 ('branch', 'run:branch', 'boolean'), 347 ('branch', 'run:branch', 'boolean'),
348 ('command_line', 'run:command_line'),
323 ('concurrency', 'run:concurrency', 'list'), 349 ('concurrency', 'run:concurrency', 'list'),
350 ('context', 'run:context'),
324 ('cover_pylib', 'run:cover_pylib', 'boolean'), 351 ('cover_pylib', 'run:cover_pylib', 'boolean'),
325 ('data_file', 'run:data_file'), 352 ('data_file', 'run:data_file'),
326 ('debug', 'run:debug', 'list'), 353 ('debug', 'run:debug', 'list'),
327 ('disable_warnings', 'run:disable_warnings', 'list'), 354 ('disable_warnings', 'run:disable_warnings', 'list'),
355 ('dynamic_context', 'run:dynamic_context'),
328 ('note', 'run:note'), 356 ('note', 'run:note'),
329 ('parallel', 'run:parallel', 'boolean'), 357 ('parallel', 'run:parallel', 'boolean'),
330 ('plugins', 'run:plugins', 'list'), 358 ('plugins', 'run:plugins', 'list'),
359 ('relative_files', 'run:relative_files', 'boolean'),
331 ('run_include', 'run:include', 'list'), 360 ('run_include', 'run:include', 'list'),
332 ('run_omit', 'run:omit', 'list'), 361 ('run_omit', 'run:omit', 'list'),
333 ('source', 'run:source', 'list'), 362 ('source', 'run:source', 'list'),
334 ('timid', 'run:timid', 'boolean'), 363 ('timid', 'run:timid', 'boolean'),
364 ('_crash', 'run:_crash'),
335 365
336 # [report] 366 # [report]
337 ('exclude_list', 'report:exclude_lines', 'regexlist'), 367 ('exclude_list', 'report:exclude_lines', 'regexlist'),
338 ('fail_under', 'report:fail_under', 'float'), 368 ('fail_under', 'report:fail_under', 'float'),
339 ('ignore_errors', 'report:ignore_errors', 'boolean'), 369 ('ignore_errors', 'report:ignore_errors', 'boolean'),
340 ('partial_always_list', 'report:partial_branches_always', 'regexlist'), 370 ('partial_always_list', 'report:partial_branches_always', 'regexlist'),
341 ('partial_list', 'report:partial_branches', 'regexlist'), 371 ('partial_list', 'report:partial_branches', 'regexlist'),
342 ('precision', 'report:precision', 'int'), 372 ('precision', 'report:precision', 'int'),
373 ('report_contexts', 'report:contexts', 'list'),
343 ('report_include', 'report:include', 'list'), 374 ('report_include', 'report:include', 'list'),
344 ('report_omit', 'report:omit', 'list'), 375 ('report_omit', 'report:omit', 'list'),
345 ('show_missing', 'report:show_missing', 'boolean'), 376 ('show_missing', 'report:show_missing', 'boolean'),
346 ('skip_covered', 'report:skip_covered', 'boolean'), 377 ('skip_covered', 'report:skip_covered', 'boolean'),
378 ('skip_empty', 'report:skip_empty', 'boolean'),
347 ('sort', 'report:sort'), 379 ('sort', 'report:sort'),
348 380
349 # [html] 381 # [html]
350 ('extra_css', 'html:extra_css'), 382 ('extra_css', 'html:extra_css'),
351 ('html_dir', 'html:directory'), 383 ('html_dir', 'html:directory'),
352 ('html_title', 'html:title'), 384 ('html_title', 'html:title'),
385 ('show_contexts', 'html:show_contexts', 'boolean'),
353 386
354 # [xml] 387 # [xml]
355 ('xml_output', 'xml:output'), 388 ('xml_output', 'xml:output'),
356 ('xml_package_depth', 'xml:package_depth', 'int'), 389 ('xml_package_depth', 'xml:package_depth', 'int'),
390
391 # [json]
392 ('json_output', 'json:output'),
393 ('json_pretty_print', 'json:pretty_print', 'boolean'),
394 ('json_show_contexts', 'json:show_contexts', 'boolean'),
357 ] 395 ]
358 396
359 def _set_attr_from_config_option(self, cp, attr, where, type_=''): 397 def _set_attr_from_config_option(self, cp, attr, where, type_=''):
360 """Set an attribute on self if it exists in the ConfigParser. 398 """Set an attribute on self if it exists in the ConfigParser.
361 399
423 461
424 # If we get here, we didn't find the option. 462 # If we get here, we didn't find the option.
425 raise CoverageException("No such option: %r" % option_name) 463 raise CoverageException("No such option: %r" % option_name)
426 464
427 465
466 def config_files_to_try(config_file):
467 """What config files should we try to read?
468
469 Returns a list of tuples:
470 (filename, is_our_file, was_file_specified)
471 """
472
473 # Some API users were specifying ".coveragerc" to mean the same as
474 # True, so make it so.
475 if config_file == ".coveragerc":
476 config_file = True
477 specified_file = (config_file is not True)
478 if not specified_file:
479 # No file was specified. Check COVERAGE_RCFILE.
480 config_file = os.environ.get('COVERAGE_RCFILE')
481 if config_file:
482 specified_file = True
483 if not specified_file:
484 # Still no file specified. Default to .coveragerc
485 config_file = ".coveragerc"
486 files_to_try = [
487 (config_file, True, specified_file),
488 ("setup.cfg", False, False),
489 ("tox.ini", False, False),
490 ("pyproject.toml", False, False),
491 ]
492 return files_to_try
493
494
428 def read_coverage_config(config_file, **kwargs): 495 def read_coverage_config(config_file, **kwargs):
429 """Read the coverage.py configuration. 496 """Read the coverage.py configuration.
430 497
431 Arguments: 498 Arguments:
432 config_file: a boolean or string, see the `Coverage` class for the 499 config_file: a boolean or string, see the `Coverage` class for the
433 tricky details. 500 tricky details.
434 all others: keyword arguments from the `Coverage` class, used for 501 all others: keyword arguments from the `Coverage` class, used for
435 setting values in the configuration. 502 setting values in the configuration.
436 503
437 Returns: 504 Returns:
438 config_file, config: 505 config:
439 config_file is the value to use for config_file in other
440 invocations of coverage.
441
442 config is a CoverageConfig object read from the appropriate 506 config is a CoverageConfig object read from the appropriate
443 configuration file. 507 configuration file.
444 508
445 """ 509 """
446 # Build the configuration from a number of sources: 510 # Build the configuration from a number of sources:
447 # 1) defaults: 511 # 1) defaults:
448 config = CoverageConfig() 512 config = CoverageConfig()
449 513
450 # 2) from a file: 514 # 2) from a file:
451 if config_file: 515 if config_file:
452 # Some API users were specifying ".coveragerc" to mean the same as 516 files_to_try = config_files_to_try(config_file)
453 # True, so make it so. 517
454 if config_file == ".coveragerc": 518 for fname, our_file, specified_file in files_to_try:
455 config_file = True
456 specified_file = (config_file is not True)
457 if not specified_file:
458 config_file = ".coveragerc"
459
460 for fname, our_file in [(config_file, True),
461 ("setup.cfg", False),
462 ("tox.ini", False)]:
463 config_read = config.from_file(fname, our_file=our_file) 519 config_read = config.from_file(fname, our_file=our_file)
464 is_config_file = fname == config_file
465
466 if not config_read and is_config_file and specified_file:
467 raise CoverageException("Couldn't read '%s' as a config file" % fname)
468
469 if config_read: 520 if config_read:
470 break 521 break
471 522 if specified_file:
523 raise CoverageException("Couldn't read '%s' as a config file" % fname)
524
525 # $set_env.py: COVERAGE_DEBUG - Options for --debug.
472 # 3) from environment variables: 526 # 3) from environment variables:
473 env_data_file = os.environ.get('COVERAGE_FILE') 527 env_data_file = os.environ.get('COVERAGE_FILE')
474 if env_data_file: 528 if env_data_file:
475 config.data_file = env_data_file 529 config.data_file = env_data_file
476 debugs = os.environ.get('COVERAGE_DEBUG') 530 debugs = os.environ.get('COVERAGE_DEBUG')
483 # Once all the config has been collected, there's a little post-processing 537 # Once all the config has been collected, there's a little post-processing
484 # to do. 538 # to do.
485 config.data_file = os.path.expanduser(config.data_file) 539 config.data_file = os.path.expanduser(config.data_file)
486 config.html_dir = os.path.expanduser(config.html_dir) 540 config.html_dir = os.path.expanduser(config.html_dir)
487 config.xml_output = os.path.expanduser(config.xml_output) 541 config.xml_output = os.path.expanduser(config.xml_output)
488 542 config.paths = collections.OrderedDict(
489 return config_file, config 543 (k, [os.path.expanduser(f) for f in v])
544 for k, v in config.paths.items()
545 )
546
547 return config

eric ide

mercurial