diff options
-rw-r--r-- | CHANGES.txt | 4 | ||||
-rw-r--r-- | coverage/config.py | 27 | ||||
-rw-r--r-- | doc/config.rst | 5 | ||||
-rw-r--r-- | test/test_config.py | 24 |
4 files changed, 57 insertions, 3 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 679f1ed8..ca819f2e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -24,6 +24,9 @@ Version 3.6b1 - The HTML report's title can now be set in the configuration file, with the ``--title`` switch on the command line, or via the API. +- Configuration files now support substitution of environment variables, using + syntax like ``${WORD}``. Closes `issue 97`_. + - Embarrassingly, the `[xml] output=' setting in the .coveragerc file simply didn't work. Now it does. @@ -89,6 +92,7 @@ Version 3.6b1 .. _issue 67: https://bitbucket.org/ned/coveragepy/issue/67/xml-report-filenames-may-be-generated .. _issue 82: https://bitbucket.org/ned/coveragepy/issue/82/tokenerror-when-generating-html-report .. _issue 89: https://bitbucket.org/ned/coveragepy/issue/89/on-windows-all-packages-are-reported-in +.. _issue 97: https://bitbucket.org/ned/coveragepy/issue/97/allow-environment-variables-to-be .. _issue 111: https://bitbucket.org/ned/coveragepy/issue/111/when-installing-coverage-with-pip-not .. _issue 137: https://bitbucket.org/ned/coveragepy/issue/137/provide-docs-with-source-distribution .. _issue 139: https://bitbucket.org/ned/coveragepy/issue/139/easy-check-for-a-certain-coverage-in-tests diff --git a/coverage/config.py b/coverage/config.py index 8f1f6710..8edd6c65 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -1,6 +1,6 @@ """Config file for coverage.py""" -import os, sys +import os, re, sys from coverage.backward import string_class, iitems # In py3, # ConfigParser was renamed to the more-standard configparser @@ -10,7 +10,7 @@ except ImportError: import ConfigParser as configparser -class HandyConfigParser(configparser.ConfigParser): +class HandyConfigParser(configparser.RawConfigParser): """Our specialization of ConfigParser.""" def read(self, filename): @@ -18,7 +18,28 @@ class HandyConfigParser(configparser.ConfigParser): kwargs = {} if sys.version_info >= (3, 2): kwargs['encoding'] = "utf-8" - configparser.ConfigParser.read(self, filename, **kwargs) + configparser.RawConfigParser.read(self, filename, **kwargs) + + def get(self, *args, **kwargs): + v = configparser.RawConfigParser.get(self, *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] + 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) + return v def getlist(self, section, option): """Read a list of strings. diff --git a/doc/config.rst b/doc/config.rst index 159a42f5..f6d8f1fc 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -39,6 +39,11 @@ values on multiple lines. Boolean values can be specified as ``on``, ``off``, ``true``, ``false``, ``1``, or ``0`` and are case-insensitive. +Environment variables can be substituted in by using dollar signs: ``$WORD`` +``${WORD}`` will be replaced with the value of ``WORD`` in the environment. +A dollar sign can be inserted with ``$$``. Missing environment variables +will result in empty strings with no error. + Many sections and values correspond roughly to commands and options in the :ref:`command-line interface <cmd>`. diff --git a/test/test_config.py b/test/test_config.py index 19e37ab9..1fdc2ce2 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -98,6 +98,30 @@ class ConfigTest(CoverageTest): """) self.assertRaises(CoverageException, coverage.coverage) + def test_environment_vars_in_config(self): + # Config files can have $envvars in them. + self.make_file(".coveragerc", """\ + [run] + data_file = $DATA_FILE.fooey + branch = $OKAY + [report] + exclude_lines = + the_$$one + another${THING} + x${THING}y + x${NOTHING}y + huh$${X}what + """) + self.set_environ("DATA_FILE", "hello-world") + self.set_environ("THING", "ZZZ") + self.set_environ("OKAY", "yes") + cov = coverage.coverage() + self.assertEqual(cov.config.data_file, "hello-world.fooey") + self.assertEqual(cov.config.branch, True) + self.assertEqual(cov.config.exclude_list, + ["the_$one", "anotherZZZ", "xZZZy", "xy", "huh${X}what"] + ) + class ConfigFileTest(CoverageTest): """Tests of the config file settings in particular.""" |