summaryrefslogtreecommitdiff
path: root/coverage/misc.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/misc.py')
-rw-r--r--coverage/misc.py69
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):