summaryrefslogtreecommitdiff
path: root/coverage/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/config.py')
-rw-r--r--coverage/config.py108
1 files changed, 70 insertions, 38 deletions
diff --git a/coverage/config.py b/coverage/config.py
index 7b142671..cd66697d 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -1,27 +1,28 @@
+# 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 coverage.backward import string_class, iitems
-from coverage.misc import CoverageException
+import collections
+import os
+import re
+import sys
+from coverage.backward import configparser, iitems, string_class
+from coverage.misc import CoverageException, isolate_module
-# In py3, ConfigParser was renamed to the more-standard configparser
-try:
- import configparser
-except ImportError:
- import ConfigParser as configparser
+os = isolate_module(os)
class HandyConfigParser(configparser.RawConfigParser):
"""Our specialization of ConfigParser."""
def __init__(self, section_prefix):
- # pylint: disable=super-init-not-called
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"
@@ -61,7 +62,7 @@ class HandyConfigParser(configparser.RawConfigParser):
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)
+ word = next(w for w in m.groups() if w is not None) # pragma: part covered
if word == "$":
return "$"
else:
@@ -112,10 +113,8 @@ class HandyConfigParser(configparser.RawConfigParser):
re.compile(value)
except re.error as e:
raise CoverageException(
- "Invalid [%s].%s value %r: %s" % (
- section, option, value, e
- )
- )
+ "Invalid [%s].%s value %r: %s" % (section, option, value, e)
+ )
if value:
value_list.append(value)
return value_list
@@ -124,12 +123,12 @@ class HandyConfigParser(configparser.RawConfigParser):
# The default line exclusion regexes.
DEFAULT_EXCLUDE = [
r'(?i)#\s*pragma[:\s]?\s*no\s*cover',
- ]
+]
# The default partial branch regexes, to be modified by the user.
DEFAULT_PARTIAL = [
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
@@ -137,7 +136,7 @@ DEFAULT_PARTIAL = [
DEFAULT_PARTIAL_ALWAYS = [
'while (True|1|False|0):',
'if (True|1|False|0):',
- ]
+]
class CoverageConfig(object):
@@ -158,11 +157,12 @@ class CoverageConfig(object):
self.concurrency = None
self.cover_pylib = False
self.data_file = ".coverage"
- self.parallel = False
- self.timid = False
- self.source = None
self.debug = []
+ self.note = None
+ self.parallel = False
self.plugins = []
+ self.source = None
+ self.timid = False
# Defaults for [report]
self.exclude_list = DEFAULT_EXCLUDE[:]
@@ -170,15 +170,15 @@ class CoverageConfig(object):
self.ignore_errors = False
self.include = None
self.omit = None
- self.partial_list = DEFAULT_PARTIAL[:]
self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
+ self.partial_list = DEFAULT_PARTIAL[:]
self.precision = 0
self.show_missing = False
self.skip_covered = False
# Defaults for [html]
- self.html_dir = "htmlcov"
self.extra_css = None
+ self.html_dir = "htmlcov"
self.html_title = "Coverage report"
# Defaults for [xml]
@@ -215,9 +215,7 @@ class CoverageConfig(object):
try:
files_read = cp.read(filename)
except configparser.Error as err:
- raise CoverageException(
- "Couldn't read config file %s: %s" % (filename, err)
- )
+ raise CoverageException("Couldn't read config file %s: %s" % (filename, err))
if not files_read:
return False
@@ -227,9 +225,24 @@ class CoverageConfig(object):
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)
- )
+ 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:
+ 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'):
@@ -258,10 +271,11 @@ class CoverageConfig(object):
('cover_pylib', 'run:cover_pylib', 'boolean'),
('data_file', 'run:data_file'),
('debug', 'run:debug', 'list'),
- ('plugins', 'run:plugins', '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'),
@@ -271,35 +285,44 @@ class CoverageConfig(object):
('ignore_errors', 'report:ignore_errors', 'boolean'),
('include', 'report:include', 'list'),
('omit', 'report:omit', 'list'),
- ('partial_list', 'report:partial_branches', 'regexlist'),
('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]
- ('html_dir', 'html:directory'),
('extra_css', 'html:extra_css'),
+ ('html_dir', 'html:directory'),
('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_=''):
"""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, {})
- # TODO: docs for this.
- def __setitem__(self, option_name, value):
+ 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]
@@ -316,8 +339,17 @@ class CoverageConfig(object):
# If we get here, we didn't find the option.
raise CoverageException("No such option: %r" % option_name)
- # TODO: docs for this.
- def __getitem__(self, 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]