diff options
-rw-r--r-- | coverage/config.py | 19 | ||||
-rw-r--r-- | coverage/misc.py | 37 | ||||
-rw-r--r-- | tests/test_misc.py | 18 |
3 files changed, 56 insertions, 18 deletions
diff --git a/coverage/config.py b/coverage/config.py index 061fa304..69c929b4 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -10,6 +10,7 @@ import re 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 os = isolate_module(os) @@ -85,23 +86,7 @@ class HandyConfigParser(configparser.RawConfigParser): 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) return v def getlist(self, section, option): diff --git a/coverage/misc.py b/coverage/misc.py index ab950846..e2031852 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -8,6 +8,7 @@ import hashlib import inspect import locale import os +import re import sys import types @@ -250,6 +251,42 @@ def _needs_to_implement(that, func_name): ) +def substitute_variables(text, variables=os.environ): + """Substitute ``${VAR}`` variables in `text` with their values. + + Variables in the text can take a number of shell-inspired forms:: + + $VAR + ${VAR} + + A dollar can be inserted with ``$$``. + + `variables` is a dictionary of variable values, defaulting to the + environment variables. + + Returns the resulting text with values substituted. + + """ + 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 variables.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. + ) + """ + text = re.sub(dollar_pattern, dollar_replace, text) + return text + + class BaseCoverageException(Exception): """The base of all Coverage exceptions.""" pass diff --git a/tests/test_misc.py b/tests/test_misc.py index 1d01537b..77200b3c 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -6,7 +6,7 @@ import pytest from coverage.misc import contract, dummy_decorator_with_args, file_be_gone -from coverage.misc import format_lines, Hasher, one_of +from coverage.misc import format_lines, Hasher, one_of, substitute_variables from tests.coveragetest import CoverageTest @@ -135,3 +135,19 @@ class ContractTest(CoverageTest): ]) def test_format_lines(statements, lines, result): assert format_lines(statements, lines) == result + + +@pytest.mark.parametrize("before, after", [ + ("Nothing to do", "Nothing to do"), + ("Dollar: $$", "Dollar: $"), + ("Simple: $FOO is fooey", "Simple: fooey is fooey"), + ("Braced: X${FOO}X.", "Braced: XfooeyX."), + ("Missing: x$NOTHING is x", "Missing: x is x"), + ("Multiple: $$ $FOO $BAR ${FOO}", "Multiple: $ fooey xyzzy fooey"), +]) +def test_substitute_variables(before, after): + variables = { + 'FOO': 'fooey', + 'BAR': 'xyzzy', + } + assert substitute_variables(before, variables) == after |