diff options
Diffstat (limited to 'coverage/misc.py')
-rw-r--r-- | coverage/misc.py | 69 |
1 files changed, 55 insertions, 14 deletions
diff --git a/coverage/misc.py b/coverage/misc.py index 28aa3b06..a923829d 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Miscellaneous stuff for coverage.py.""" @@ -8,6 +8,7 @@ import hashlib import inspect import locale import os +import re import sys import types @@ -69,11 +70,11 @@ if env.TESTING: """Ensure that only one of the argnames is non-None.""" def _decorator(func): argnameset = set(name.strip() for name in argnames.split(",")) - def _wrapped(*args, **kwargs): + def _wrapper(*args, **kwargs): vals = [kwargs.get(name) for name in argnameset] assert sum(val is not None for val in vals) == 1 return func(*args, **kwargs) - return _wrapped + return _wrapper return _decorator else: # pragma: not testing # We aren't using real PyContracts, so just define our decorators as @@ -148,13 +149,12 @@ def expensive(fn): if env.TESTING: attr = "_once_" + fn.__name__ - def _wrapped(self): - """Inner function that checks the cache.""" + def _wrapper(self): if hasattr(self, attr): raise AssertionError("Shouldn't have called %s more than once" % fn.__name__) setattr(self, attr, True) return fn(self) - return _wrapped + return _wrapper else: return fn # pragma: not testing @@ -226,6 +226,7 @@ class Hasher(object): continue self.update(k) self.update(a) + self.md5.update(b'.') def hexdigest(self): """Retrieve the hex digest of the hash.""" @@ -249,14 +250,54 @@ def _needs_to_implement(that, func_name): ) -class SimpleRepr(object): - """A mixin implementing a simple __repr__.""" - def __repr__(self): - return "<{klass} @{id:x} {attrs}>".format( - klass=self.__class__.__name__, - id=id(self) & 0xFFFFFF, - attrs=" ".join("{}={!r}".format(k, v) for k, v in self.__dict__.items()), - ) +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} + ${VAR?} strict: an error if VAR isn't defined. + ${VAR-missing} defaulted: "missing" if VAR isn't defined. + + 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 = ''.join(m.group(name) or '' for name in ['v1', 'v2', 'char']) + if word == "$": + return "$" + else: + strict = bool(m.group('strict')) + if strict: + if word not in variables: + msg = "Variable {} is undefined: {}".format(word, text) + raise CoverageException(msg) + return variables.get(word, m.group('defval') or '') + + dollar_pattern = r"""(?x) # Use extended regex syntax + \$(?: # A dollar sign, then + (?P<v1>\w+) | # a plain word, + (?P<char>\$) | # or a dollar sign. + { # or a {-wrapped word, + (?P<v2>\w+) + (?: + (?P<strict>\?) # with a strict marker + | + -(?P<defval>[^}]*) # or a default value + )? + } + ) + """ + text = re.sub(dollar_pattern, dollar_replace, text) + return text class BaseCoverageException(Exception): |