Commits

Ned Batchelder committed ff298b5

Config files now can use environment variables. #97.

Comments (0)

Files changed (4)

 - 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.
 
 .. _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

coverage/config.py

 """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
     import ConfigParser as configparser
 
 
-class HandyConfigParser(configparser.ConfigParser):
+class HandyConfigParser(configparser.RawConfigParser):
     """Our specialization of ConfigParser."""
 
     def read(self, filename):
         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.
 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>`.
 

test/test_config.py

             """)
         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."""