eric6/DebugClients/Python/coverage/misc.py

changeset 7427
362cd1b6f81a
parent 6942
2602857055c5
--- a/eric6/DebugClients/Python/coverage/misc.py	Wed Feb 19 19:38:36 2020 +0100
+++ b/eric6/DebugClients/Python/coverage/misc.py	Sat Feb 22 14:27:42 2020 +0100
@@ -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,10 @@
 import inspect
 import locale
 import os
+import os.path
+import random
+import re
+import socket
 import sys
 import types
 
@@ -45,9 +49,14 @@
     return _decorator
 
 
+# Environment COVERAGE_NO_CONTRACTS=1 can turn off contracts while debugging
+# tests to remove noise from stack traces.
+# $set_env.py: COVERAGE_NO_CONTRACTS - Disable PyContracts to simplify stack traces.
+USE_CONTRACTS = env.TESTING and not bool(int(os.environ.get("COVERAGE_NO_CONTRACTS", 0)))
+
 # Use PyContracts for assertion testing on parameters and returns, but only if
 # we are running our own test suite.
-if env.TESTING:
+if USE_CONTRACTS:
     from contracts import contract              # pylint: disable=unused-import
     from contracts import new_contract as raw_new_contract
 
@@ -69,11 +78,11 @@
         """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
@@ -100,44 +109,6 @@
         return "%d-%d" % (start, end)
 
 
-def format_lines(statements, lines):
-    """Nicely format a list of line numbers.
-
-    Format a list of line numbers for printing by coalescing groups of lines as
-    long as the lines represent consecutive statements.  This will coalesce
-    even if there are gaps between statements.
-
-    For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and
-    `lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14".
-
-    Both `lines` and `statements` can be any iterable. All of the elements of
-    `lines` must be in `statements`, and all of the values must be positive
-    integers.
-
-    """
-    statements = sorted(statements)
-    lines = sorted(lines)
-
-    pairs = []
-    start = None
-    lidx = 0
-    for stmt in statements:
-        if lidx >= len(lines):
-            break
-        if stmt == lines[lidx]:
-            lidx += 1
-            if not start:
-                start = stmt
-            end = stmt
-        elif start:
-            pairs.append((start, end))
-            start = None
-    if start:
-        pairs.append((start, end))
-    ret = ', '.join(map(nice_pair, pairs))
-    return ret
-
-
 def expensive(fn):
     """A decorator to indicate that a method shouldn't be called more than once.
 
@@ -148,13 +119,12 @@
     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
 
@@ -181,6 +151,20 @@
             raise
 
 
+def ensure_dir(directory):
+    """Make sure the directory exists.
+
+    If `directory` is None or empty, do nothing.
+    """
+    if directory and not os.path.isdir(directory):
+        os.makedirs(directory)
+
+
+def ensure_dir_for_file(path):
+    """Make sure the directory for the path exists."""
+    ensure_dir(os.path.dirname(path))
+
+
 def output_encoding(outfile=None):
     """Determine the encoding to use for output written to `outfile` or stdout."""
     if outfile is None:
@@ -193,6 +177,26 @@
     return encoding
 
 
+def filename_suffix(suffix):
+    """Compute a filename suffix for a data file.
+
+    If `suffix` is a string or None, simply return it. If `suffix` is True,
+    then build a suffix incorporating the hostname, process id, and a random
+    number.
+
+    Returns a string or None.
+
+    """
+    if suffix is True:
+        # If data_suffix was a simple true value, then make a suffix with
+        # plenty of distinguishing information.  We do this here in
+        # `save()` at the last minute so that the pid will be correct even
+        # if the process forks.
+        dice = random.Random(os.urandom(8)).randint(0, 999999)
+        suffix = "%s.%s.%06d" % (socket.gethostname(), os.getpid(), dice)
+    return suffix
+
+
 class Hasher(object):
     """Hashes Python data into md5."""
     def __init__(self):
@@ -226,6 +230,7 @@
                     continue
                 self.update(k)
                 self.update(a)
+        self.md5.update(b'.')
 
     def hexdigest(self):
         """Retrieve the hex digest of the hash."""
@@ -249,14 +254,67 @@
         )
 
 
-class SimpleRepr(object):
-    """A mixin implementing a simple __repr__."""
+class DefaultValue(object):
+    """A sentinel object to use for unusual default-value needs.
+
+    Construct with a string that will be used as the repr, for display in help
+    and Sphinx output.
+
+    """
+    def __init__(self, display_as):
+        self.display_as = display_as
+
     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()),
-            )
+        return self.display_as
+
+
+def substitute_variables(text, variables):
+    """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.
+        $$                  just a dollar sign.
+
+    `variables` is a dictionary of variable values.
+
+    Returns the resulting text with values substituted.
+
+    """
+    dollar_pattern = r"""(?x)   # Use extended regex syntax
+        \$                      # A dollar sign,
+        (?:                     # then
+            (?P<dollar>\$) |        # a dollar sign, or
+            (?P<word1>\w+) |        # a plain word, or
+            {                       # a {-wrapped
+                (?P<word2>\w+)          # word,
+                (?:
+                    (?P<strict>\?) |        # with a strict marker
+                    -(?P<defval>[^}]*)      # or a default value
+                )?                      # maybe.
+            }
+        )
+        """
+
+    def dollar_replace(match):
+        """Called for each $replacement."""
+        # Only one of the groups will have matched, just get its text.
+        word = next(g for g in match.group('dollar', 'word1', 'word2') if g)
+        if word == "$":
+            return "$"
+        elif word in variables:
+            return variables[word]
+        elif match.group('strict'):
+            msg = "Variable {} is undefined: {!r}".format(word, text)
+            raise CoverageException(msg)
+        else:
+            return match.group('defval')
+
+    text = re.sub(dollar_pattern, dollar_replace, text)
+    return text
 
 
 class BaseCoverageException(Exception):
@@ -265,7 +323,7 @@
 
 
 class CoverageException(BaseCoverageException):
-    """A run-of-the-mill exception specific to coverage.py."""
+    """An exception raised by a coverage.py function."""
     pass
 
 

eric ide

mercurial