--- a/DebugClients/Python3/coverage/config.py Sun Oct 04 13:35:09 2015 +0200 +++ b/DebugClients/Python3/coverage/config.py Sun Oct 04 22:37:56 2015 +0200 @@ -1,31 +1,66 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + """Config file for coverage.py""" -import os, re, sys -from .backward import string_class, iitems +import collections +import os +import re +import sys -# In py3, # ConfigParser was renamed to the more-standard configparser -try: - import configparser # pylint: disable=F0401 -except ImportError: - import ConfigParser as configparser +from coverage.backward import configparser, iitems, string_class +from coverage.misc import CoverageException class HandyConfigParser(configparser.RawConfigParser): """Our specialization of ConfigParser.""" + def __init__(self, section_prefix): + configparser.RawConfigParser.__init__(self) + self.section_prefix = section_prefix + def read(self, filename): - """Read a filename as UTF-8 configuration data.""" + """Read a file name as UTF-8 configuration data.""" kwargs = {} if sys.version_info >= (3, 2): kwargs['encoding'] = "utf-8" return configparser.RawConfigParser.read(self, filename, **kwargs) - def get(self, *args, **kwargs): - v = configparser.RawConfigParser.get(self, *args, **kwargs) + def has_option(self, section, option): + section = self.section_prefix + section + return configparser.RawConfigParser.has_option(self, section, option) + + def has_section(self, section): + section = self.section_prefix + section + return configparser.RawConfigParser.has_section(self, section) + + def options(self, section): + section = self.section_prefix + section + return configparser.RawConfigParser.options(self, section) + + def get_section(self, section): + """Get the contents of a section, as a dictionary.""" + d = {} + for opt in self.options(section): + d[opt] = self.get(section, opt) + return d + + def get(self, section, *args, **kwargs): + """Get a value, replacing environment variables also. + + The arguments are the same as `RawConfigParser.get`, but in the found + value, ``$WORD`` or ``${WORD}`` are replaced by the value of the + environment variable ``WORD``. + + Returns the finished value. + + """ + section = self.section_prefix + section + v = configparser.RawConfigParser.get(self, section, *args, **kwargs) def dollar_replace(m): """Called for each $replacement.""" # Only one of the groups will have matched, just get its text. - word = [w for w in m.groups() if w is not None][0] + word = next(w for w in m.groups() if w is not None) # pragma: part covered if word == "$": return "$" else: @@ -59,28 +94,39 @@ values.append(value) return values - def getlinelist(self, section, option): - """Read a list of full-line strings. + def getregexlist(self, section, option): + """Read a list of full-line regexes. The value of `section` and `option` is treated as a newline-separated - list of strings. Each value is stripped of whitespace. + list of regexes. Each value is stripped of whitespace. Returns the list of strings. """ - value_list = self.get(section, option) - return list(filter(None, value_list.split('\n'))) + line_list = self.get(section, option) + value_list = [] + for value in line_list.splitlines(): + value = value.strip() + try: + re.compile(value) + except re.error as e: + raise CoverageException( + "Invalid [%s].%s value %r: %s" % (section, option, value, e) + ) + if value: + value_list.append(value) + return value_list -# The default line exclusion regexes +# The default line exclusion regexes. DEFAULT_EXCLUDE = [ - '(?i)# *pragma[: ]*no *cover', - ] + r'(?i)#\s*pragma[:\s]?\s*no\s*cover', +] # The default partial branch regexes, to be modified by the user. DEFAULT_PARTIAL = [ - '(?i)# *pragma[: ]*no *branch', - ] + r'(?i)#\s*pragma[:\s]?\s*no\s*branch', +] # The default partial branch regexes, based on Python semantics. # These are any Python branching constructs that can't actually execute all @@ -88,7 +134,7 @@ DEFAULT_PARTIAL_ALWAYS = [ 'while (True|1|False|0):', 'if (True|1|False|0):', - ] +] class CoverageConfig(object): @@ -106,44 +152,44 @@ # Defaults for [run] self.branch = False + self.concurrency = None self.cover_pylib = False self.data_file = ".coverage" + self.debug = [] + self.note = None self.parallel = False + self.plugins = [] + self.source = None self.timid = False - self.source = None - self.debug = [] # Defaults for [report] self.exclude_list = DEFAULT_EXCLUDE[:] + self.fail_under = 0 self.ignore_errors = False self.include = None self.omit = None + self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:] self.partial_list = DEFAULT_PARTIAL[:] - self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:] self.precision = 0 self.show_missing = False + self.skip_covered = False # Defaults for [html] + self.extra_css = None self.html_dir = "htmlcov" - self.extra_css = None self.html_title = "Coverage report" # Defaults for [xml] self.xml_output = "coverage.xml" + self.xml_package_depth = 99 # Defaults for [paths] self.paths = {} - def from_environment(self, env_var): - """Read configuration from the `env_var` environment variable.""" - # Timidity: for nose users, read an environment variable. This is a - # cheap hack, since the rest of the command line arguments aren't - # recognized, but it solves some users' problems. - env = os.environ.get(env_var, '') - if env: - self.timid = ('--timid' in env) + # Options for plugins + self.plugin_options = {} - MUST_BE_LIST = ["omit", "include", "debug"] + MUST_BE_LIST = ["omit", "include", "debug", "plugins"] def from_args(self, **kwargs): """Read config values from `kwargs`.""" @@ -153,61 +199,165 @@ v = [v] setattr(self, k, v) - def from_file(self, filename): + def from_file(self, filename, section_prefix=""): """Read configuration from a .rc file. `filename` is a file name to read. + Returns True or False, whether the file could be read. + """ self.attempted_config_files.append(filename) - cp = HandyConfigParser() - files_read = cp.read(filename) - if files_read is not None: # return value changed in 2.4 - self.config_files.extend(files_read) + cp = HandyConfigParser(section_prefix) + try: + files_read = cp.read(filename) + except configparser.Error 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) + + try: + for option_spec in self.CONFIG_FILE_OPTIONS: + self._set_attr_from_config_option(cp, *option_spec) + except ValueError as err: + raise CoverageException("Couldn't read config file %s: %s" % (filename, err)) + # Check that there are no unrecognized options. + all_options = collections.defaultdict(set) for option_spec in self.CONFIG_FILE_OPTIONS: - self.set_attr_from_config_option(cp, *option_spec) + section, option = option_spec[1].split(":") + all_options[section].add(option) + + for section, options in iitems(all_options): + if cp.has_section(section): + for unknown in set(cp.options(section)) - options: + if section_prefix: + section = section_prefix + section + raise CoverageException( + "Unrecognized option '[%s] %s=' in config file %s" % ( + section, unknown, filename + ) + ) # [paths] is special if cp.has_section('paths'): for option in cp.options('paths'): self.paths[option] = cp.getlist('paths', option) + # plugins can have options + for plugin in self.plugins: + if cp.has_section(plugin): + self.plugin_options[plugin] = cp.get_section(plugin) + + return True + CONFIG_FILE_OPTIONS = [ + # These are *args for _set_attr_from_config_option: + # (attr, where, type_="") + # + # attr is the attribute to set on the CoverageConfig object. + # where is the section:name to read from the configuration file. + # type_ is the optional type to apply, by using .getTYPE to read the + # configuration value from the file. + # [run] ('branch', 'run:branch', 'boolean'), + ('concurrency', 'run:concurrency'), ('cover_pylib', 'run:cover_pylib', 'boolean'), ('data_file', 'run:data_file'), ('debug', 'run:debug', 'list'), ('include', 'run:include', 'list'), + ('note', 'run:note'), ('omit', 'run:omit', 'list'), ('parallel', 'run:parallel', 'boolean'), + ('plugins', 'run:plugins', 'list'), ('source', 'run:source', 'list'), ('timid', 'run:timid', 'boolean'), # [report] - ('exclude_list', 'report:exclude_lines', 'linelist'), + ('exclude_list', 'report:exclude_lines', 'regexlist'), + ('fail_under', 'report:fail_under', 'int'), ('ignore_errors', 'report:ignore_errors', 'boolean'), ('include', 'report:include', 'list'), ('omit', 'report:omit', 'list'), - ('partial_list', 'report:partial_branches', 'linelist'), - ('partial_always_list', 'report:partial_branches_always', 'linelist'), + ('partial_always_list', 'report:partial_branches_always', 'regexlist'), + ('partial_list', 'report:partial_branches', 'regexlist'), ('precision', 'report:precision', 'int'), ('show_missing', 'report:show_missing', 'boolean'), + ('skip_covered', 'report:skip_covered', 'boolean'), # [html] + ('extra_css', 'html:extra_css'), ('html_dir', 'html:directory'), - ('extra_css', 'html:extra_css'), ('html_title', 'html:title'), # [xml] ('xml_output', 'xml:output'), - ] + ('xml_package_depth', 'xml:package_depth', 'int'), + ] - def set_attr_from_config_option(self, cp, attr, where, type_=''): + def _set_attr_from_config_option(self, cp, attr, where, type_=''): """Set an attribute on self if it exists in the ConfigParser.""" section, option = where.split(":") if cp.has_option(section, option): - method = getattr(cp, 'get'+type_) + method = getattr(cp, 'get' + type_) setattr(self, attr, method(section, option)) + + def get_plugin_options(self, plugin): + """Get a dictionary of options for the plugin named `plugin`.""" + return self.plugin_options.get(plugin, {}) + + def set_option(self, option_name, value): + """Set an option in the configuration. + + `option_name` is a colon-separated string indicating the section and + option name. For example, the ``branch`` option in the ``[run]`` + section of the config file would be indicated with `"run:branch"`. + + `value` is the new value for the option. + + """ + + # Check all the hard-coded options. + for option_spec in self.CONFIG_FILE_OPTIONS: + attr, where = option_spec[:2] + if where == option_name: + setattr(self, attr, value) + return + + # See if it's a plugin option. + plugin_name, _, key = option_name.partition(":") + if key and plugin_name in self.plugins: + self.plugin_options.setdefault(plugin_name, {})[key] = value + return + + # If we get here, we didn't find the option. + raise CoverageException("No such option: %r" % option_name) + + def get_option(self, option_name): + """Get an option from the configuration. + + `option_name` is a colon-separated string indicating the section and + option name. For example, the ``branch`` option in the ``[run]`` + section of the config file would be indicated with `"run:branch"`. + + Returns the value of the option. + + """ + + # Check all the hard-coded options. + for option_spec in self.CONFIG_FILE_OPTIONS: + attr, where = option_spec[:2] + if where == option_name: + return getattr(self, attr) + + # See if it's a plugin option. + plugin_name, _, key = option_name.partition(":") + if key and plugin_name in self.plugins: + return self.plugin_options.get(plugin_name, {}).get(key) + + # If we get here, we didn't find the option. + raise CoverageException("No such option: %r" % option_name)