diff -r dc171b1d8261 -r 362cd1b6f81a eric6/DebugClients/Python/coverage/config.py --- a/eric6/DebugClients/Python/coverage/config.py Wed Feb 19 19:38:36 2020 +0100 +++ b/eric6/DebugClients/Python/coverage/config.py Sat Feb 22 14:27:42 2020 +0100 @@ -1,15 +1,20 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Config file for coverage.py""" import collections +import copy import os +import os.path import re -import sys +from coverage import env from coverage.backward import configparser, iitems, string_class from coverage.misc import contract, CoverageException, isolate_module +from coverage.misc import substitute_variables + +from coverage.tomlconfig import TomlConfigParser, TomlDecodeError os = isolate_module(os) @@ -30,11 +35,11 @@ if our_file: self.section_prefixes.append("") - def read(self, filenames): + def read(self, filenames, encoding=None): """Read a file name as UTF-8 configuration data.""" kwargs = {} - if sys.version_info >= (3, 2): - kwargs['encoding'] = "utf-8" + if env.PYVERSION >= (3, 2): + kwargs['encoding'] = encoding or "utf-8" return configparser.RawConfigParser.read(self, filenames, **kwargs) def has_option(self, section, option): @@ -85,23 +90,7 @@ raise configparser.NoOptionError v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs) - def dollar_replace(m): - """Called for each $replacement.""" - # Only one of the groups will have matched, just get its text. - word = next(w for w in m.groups() if w is not None) # pragma: part covered - if word == "$": - return "$" - else: - return os.environ.get(word, '') - - dollar_pattern = r"""(?x) # Use extended regex syntax - \$(?: # A dollar sign, then - (?P<v1>\w+) | # a plain word, - {(?P<v2>\w+)} | # or a {-wrapped word, - (?P<char>[$]) # or a dollar sign. - ) - """ - v = re.sub(dollar_pattern, dollar_replace, v) + v = substitute_variables(v, os.environ) return v def getlist(self, section, option): @@ -172,11 +161,18 @@ operation of coverage.py. """ + # pylint: disable=too-many-instance-attributes + def __init__(self): """Initialize the configuration attributes to their defaults.""" # Metadata about the config. + # We tried to read these config files. self.attempted_config_files = [] - self.config_files = [] + # We did read these config files, but maybe didn't find any content for us. + self.config_files_read = [] + # The file that gave us our configuration. + self.config_file = None + self._config_contents = None # Defaults for [run] and [report] self._include = None @@ -184,18 +180,23 @@ # Defaults for [run] self.branch = False + self.command_line = None self.concurrency = None + self.context = None self.cover_pylib = False self.data_file = ".coverage" self.debug = [] self.disable_warnings = [] + self.dynamic_context = None self.note = None self.parallel = False self.plugins = [] - self.source = None + self.relative_files = False self.run_include = None self.run_omit = None + self.source = None self.timid = False + self._crash = None # Defaults for [report] self.exclude_list = DEFAULT_EXCLUDE[:] @@ -206,20 +207,28 @@ self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:] self.partial_list = DEFAULT_PARTIAL[:] self.precision = 0 + self.report_contexts = None self.show_missing = False self.skip_covered = False + self.skip_empty = False # Defaults for [html] self.extra_css = None self.html_dir = "htmlcov" self.html_title = "Coverage report" + self.show_contexts = False # Defaults for [xml] self.xml_output = "coverage.xml" self.xml_package_depth = 99 + # Defaults for [json] + self.json_output = "coverage.json" + self.json_pretty_print = False + self.json_show_contexts = False + # Defaults for [paths] - self.paths = {} + self.paths = collections.OrderedDict() # Options for plugins self.plugin_options = {} @@ -252,17 +261,22 @@ coverage.py settings in it. """ + _, ext = os.path.splitext(filename) + if ext == '.toml': + cp = TomlConfigParser(our_file) + else: + cp = HandyConfigParser(our_file) + self.attempted_config_files.append(filename) - cp = HandyConfigParser(our_file) try: files_read = cp.read(filename) - except configparser.Error as err: + except (configparser.Error, TomlDecodeError) as err: raise CoverageException("Couldn't read config file %s: %s" % (filename, err)) if not files_read: return False - self.config_files.extend(files_read) + self.config_files_read.extend(map(os.path.abspath, files_read)) any_set = False try: @@ -305,9 +319,20 @@ # then it was used. If we're piggybacking on someone else's file, # then it was only used if we found some settings in it. if our_file: - return True + used = True else: - return any_set + used = any_set + + if used: + self.config_file = os.path.abspath(filename) + with open(filename) as f: + self._config_contents = f.read() + + return used + + def copy(self): + """Return a copy of the configuration.""" + return copy.deepcopy(self) CONFIG_FILE_OPTIONS = [ # These are *args for _set_attr_from_config_option: @@ -320,18 +345,23 @@ # [run] ('branch', 'run:branch', 'boolean'), + ('command_line', 'run:command_line'), ('concurrency', 'run:concurrency', 'list'), + ('context', 'run:context'), ('cover_pylib', 'run:cover_pylib', 'boolean'), ('data_file', 'run:data_file'), ('debug', 'run:debug', 'list'), ('disable_warnings', 'run:disable_warnings', 'list'), + ('dynamic_context', 'run:dynamic_context'), ('note', 'run:note'), ('parallel', 'run:parallel', 'boolean'), ('plugins', 'run:plugins', 'list'), + ('relative_files', 'run:relative_files', 'boolean'), ('run_include', 'run:include', 'list'), ('run_omit', 'run:omit', 'list'), ('source', 'run:source', 'list'), ('timid', 'run:timid', 'boolean'), + ('_crash', 'run:_crash'), # [report] ('exclude_list', 'report:exclude_lines', 'regexlist'), @@ -340,20 +370,28 @@ ('partial_always_list', 'report:partial_branches_always', 'regexlist'), ('partial_list', 'report:partial_branches', 'regexlist'), ('precision', 'report:precision', 'int'), + ('report_contexts', 'report:contexts', 'list'), ('report_include', 'report:include', 'list'), ('report_omit', 'report:omit', 'list'), ('show_missing', 'report:show_missing', 'boolean'), ('skip_covered', 'report:skip_covered', 'boolean'), + ('skip_empty', 'report:skip_empty', 'boolean'), ('sort', 'report:sort'), # [html] ('extra_css', 'html:extra_css'), ('html_dir', 'html:directory'), ('html_title', 'html:title'), + ('show_contexts', 'html:show_contexts', 'boolean'), # [xml] ('xml_output', 'xml:output'), ('xml_package_depth', 'xml:package_depth', 'int'), + + # [json] + ('json_output', 'json:output'), + ('json_pretty_print', 'json:pretty_print', 'boolean'), + ('json_show_contexts', 'json:show_contexts', 'boolean'), ] def _set_attr_from_config_option(self, cp, attr, where, type_=''): @@ -425,6 +463,35 @@ raise CoverageException("No such option: %r" % option_name) +def config_files_to_try(config_file): + """What config files should we try to read? + + Returns a list of tuples: + (filename, is_our_file, was_file_specified) + """ + + # Some API users were specifying ".coveragerc" to mean the same as + # True, so make it so. + if config_file == ".coveragerc": + config_file = True + specified_file = (config_file is not True) + if not specified_file: + # No file was specified. Check COVERAGE_RCFILE. + config_file = os.environ.get('COVERAGE_RCFILE') + if config_file: + specified_file = True + if not specified_file: + # Still no file specified. Default to .coveragerc + config_file = ".coveragerc" + files_to_try = [ + (config_file, True, specified_file), + ("setup.cfg", False, False), + ("tox.ini", False, False), + ("pyproject.toml", False, False), + ] + return files_to_try + + def read_coverage_config(config_file, **kwargs): """Read the coverage.py configuration. @@ -435,10 +502,7 @@ setting values in the configuration. Returns: - config_file, config: - config_file is the value to use for config_file in other - invocations of coverage. - + config: config is a CoverageConfig object read from the appropriate configuration file. @@ -449,26 +513,16 @@ # 2) from a file: if config_file: - # Some API users were specifying ".coveragerc" to mean the same as - # True, so make it so. - if config_file == ".coveragerc": - config_file = True - specified_file = (config_file is not True) - if not specified_file: - config_file = ".coveragerc" + files_to_try = config_files_to_try(config_file) - for fname, our_file in [(config_file, True), - ("setup.cfg", False), - ("tox.ini", False)]: + for fname, our_file, specified_file in files_to_try: config_read = config.from_file(fname, our_file=our_file) - is_config_file = fname == config_file - - if not config_read and is_config_file and specified_file: + if config_read: + break + if specified_file: raise CoverageException("Couldn't read '%s' as a config file" % fname) - if config_read: - break - + # $set_env.py: COVERAGE_DEBUG - Options for --debug. # 3) from environment variables: env_data_file = os.environ.get('COVERAGE_FILE') if env_data_file: @@ -485,5 +539,9 @@ config.data_file = os.path.expanduser(config.data_file) config.html_dir = os.path.expanduser(config.html_dir) config.xml_output = os.path.expanduser(config.xml_output) + config.paths = collections.OrderedDict( + (k, [os.path.expanduser(f) for f in v]) + for k, v in config.paths.items() + ) - return config_file, config + return config