[
  {
    "path": ".gitignore",
    "content": "*.py[co]\n__pycache__/\n\n.tox/\n.pytest_cache/\n.mypy_cache/\n\ndist/\nbuild/\n*.egg-info/\n\n*.bak\n\n*.wpu\n\n.coverage\nhtmlcov\n"
  },
  {
    "path": ".travis.yml",
    "content": "dist: xenial\nlanguage: python\n\npython:\n- 2.7\n- 3.5\n- 3.6\n- 3.7\n- 3.8\n- 3.9\n- 3.10-dev\n- pypy2.7-6.0\n- pypy3.5\n\ninstall:\n- pip install tox-travis\nscript:\n- tox\n\nstages:\n- lint\n- test\n#- deploy\n\nmatrix:\n  allow_failures:\n  - env: TOXENV=flake8\n  - env: TOXENV=pylint\n  - env: TOXENV=bandit\n  - python: 3.10-dev\n\njobs:\n  include:\n  #- { stage: lint, python: 3.7, env: TOXENV=flake8 }\n  #- { stage: lint, python: 3.7, env: TOXENV=pylint }\n  #- { stage: lint, python: 3.7, env: TOXENV=bandit }\n  - { stage: lint, python: 3.7, env: TOXENV=readme }\n\n  #- stage: deploy\n  #  install: skip\n  #  script: skip\n  #  deploy:\n  #    provider: pypi\n  #    distributions: sdist bdist_wheel\n  #    user: cool-RR\n  #    password:\n  #      secure: <your-pypi-password-here-encrypted-using-the-travis-cli>\n  #    on:\n  #      tags: true\n"
  },
  {
    "path": "ADVANCED_USAGE.md",
    "content": "# Advanced Usage #\n\nUse `watch_explode` to expand values to see all their attributes or items of lists/dictionaries:\n\n```python\n@pysnooper.snoop(watch_explode=('foo', 'self'))\n```\n\n`watch_explode` will automatically guess how to expand the expression passed to it based on its class. You can be more specific by using one of the following classes:\n\n```python\nimport pysnooper\n\n@pysnooper.snoop(watch=(\n    pysnooper.Attrs('x'),    # attributes\n    pysnooper.Keys('y'),     # mapping (e.g. dict) items\n    pysnooper.Indices('z'),  # sequence (e.g. list/tuple) items\n))\n```\n\nExclude specific keys/attributes/indices with the `exclude` parameter, e.g. `Attrs('x', exclude=('_foo', '_bar'))`.\n\nAdd a slice after `Indices` to only see the values within that slice, e.g. `Indices('z')[-3:]`.\n\n```console\n$ export PYSNOOPER_DISABLED=1 # This makes PySnooper not do any snooping\n```\n\nThis will output lines like:\n\n```\nModified var:.. foo[2] = 'whatever'\nNew var:....... self.baz = 8\n```\n\nStart all snoop lines with a prefix, to grep for them easily:\n\n```python\n@pysnooper.snoop(prefix='ZZZ ')\n```\n\nRemove all machine-related data (paths, timestamps, memory addresses) to compare with other traces easily:\n\n```python\n@pysnooper.snoop(normalize=True)\n```\n\nOn multi-threaded apps identify which thread are snooped in output:\n\n```python\n@pysnooper.snoop(thread_info=True)\n```\n\nPySnooper supports decorating generators.\n\nIf you decorate a class with `snoop`, it'll automatically apply the decorator to all the methods. (Not including properties and other special cases.)\n\nYou can also customize the repr of an object:\n\n```python\ndef large(l):\n    return isinstance(l, list) and len(l) > 5\n\ndef print_list_size(l):\n    return 'list(size={})'.format(len(l))\n\ndef print_ndarray(a):\n    return 'ndarray(shape={}, dtype={})'.format(a.shape, a.dtype)\n\n@pysnooper.snoop(custom_repr=((large, print_list_size), (numpy.ndarray, print_ndarray)))\ndef sum_to_x(x):\n    l = list(range(x))\n    a = numpy.zeros((10,10))\n    return sum(l)\n\nsum_to_x(10000)\n```\n\nYou will get `l = list(size=10000)` for the list, and `a = ndarray(shape=(10, 10), dtype=float64)` for the ndarray.\nThe `custom_repr` are matched in order, if one condition matches, no further conditions will be checked.\n\nVariables and exceptions get truncated to 100 characters by default. You\ncan customize that:\n\n```python\n    @pysnooper.snoop(max_variable_length=200)\n```\n\nYou can also use `max_variable_length=None` to never truncate them.\n\nUse `relative_time=True` to show timestamps relative to start time rather than\nwall time.\n\nThe output is colored for easy viewing by default, except on Windows. Disable colors like so:\n\n```python\n    @pysnooper.snoop(color=False)\n````\n"
  },
  {
    "path": "AUTHORS",
    "content": "Ram Rachum\nOleg Butuzov\nEdward Betts\nwilfredinni\nPeter Bittner\nAlireza Ayinmehr\nChristian Zietz\nBinwei Hu\nLoukas Leontopoulos\nShlomi Fish\nAlex Hall\npohmelie\nNikita Melentev\nMike Bayer\nAndreas van Cranenburgh\nHervé Beraud\nDiego Volpatto\nAlexander Bersenev\nXiang Gao\npikez\nJonathan Reichelt Gjertsen\nGuoqiang Ding\nItamar.Raviv\niory\nMark Blakeney\nYael Mintz\nLumír 'Frenzy' Balhar\nLukas Klenk\nsizhky\nAndrej730\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Ram Rachum and collaborators\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include README.md\ninclude LICENSE\ninclude requirements.in\ninclude requirements.txt\nrecursive-include tests *.txt *.py\nprune tests/.pytest_cache\n"
  },
  {
    "path": "README.md",
    "content": "# PySnooper - Never use print for debugging again\n\n**PySnooper** is a poor man's debugger. If you've used Bash, it's like `set -x` for Python, except it's fancier.\n\nYour story: You're trying to figure out why your Python code isn't doing what you think it should be doing. You'd love to use a full-fledged debugger with breakpoints and watches, but you can't be bothered to set one up right now.\n\nYou want to know which lines are running and which aren't, and what the values of the local variables are.\n\nMost people would use `print` lines, in strategic locations, some of them showing the values of variables.\n\n**PySnooper** lets you do the same, except instead of carefully crafting the right `print` lines, you just add one decorator line to the function you're interested in. You'll get a play-by-play log of your function, including which lines ran and   when, and exactly when local variables were changed.\n\nWhat makes **PySnooper** stand out from all other code intelligence tools? You can use it in your shitty, sprawling enterprise codebase without having to do any setup. Just slap the decorator on, as shown below, and redirect the output to a dedicated log file by specifying its path as the first argument.\n\n## Example\n\nWe're writing a function that converts a number to binary, by returning a list of bits. Let's snoop on it by adding the `@pysnooper.snoop()` decorator:\n\n```python\nimport pysnooper\n\n@pysnooper.snoop()\ndef number_to_bits(number):\n    if number:\n        bits = []\n        while number:\n            number, remainder = divmod(number, 2)\n            bits.insert(0, remainder)\n        return bits\n    else:\n        return [0]\n\nnumber_to_bits(6)\n```\nThe output to stderr is:\n\n![](https://i.imgur.com/TrF3VVj.jpg)\n\nOr if you don't want to trace an entire function, you can wrap the relevant part in a `with` block:\n\n```python\nimport pysnooper\nimport random\n\ndef foo():\n    lst = []\n    for i in range(10):\n        lst.append(random.randrange(1, 1000))\n\n    with pysnooper.snoop():\n        lower = min(lst)\n        upper = max(lst)\n        mid = (lower + upper) / 2\n        print(lower, mid, upper)\n\nfoo()\n```\n\nwhich outputs something like:\n\n```\nNew var:....... i = 9\nNew var:....... lst = [681, 267, 74, 832, 284, 678, ...]\n09:37:35.881721 line        10         lower = min(lst)\nNew var:....... lower = 74\n09:37:35.882137 line        11         upper = max(lst)\nNew var:....... upper = 832\n09:37:35.882304 line        12         mid = (lower + upper) / 2\n74 453.0 832\nNew var:....... mid = 453.0\n09:37:35.882486 line        13         print(lower, mid, upper)\nElapsed time: 00:00:00.000344\n```\n\n## Features\n\nIf stderr is not easily accessible for you, you can redirect the output to a file:\n\n```python\n@pysnooper.snoop('/my/log/file.log')\n```\n\nYou can also pass a stream or a callable instead, and they'll be used.\n\nSee values of some expressions that aren't local variables:\n\n```python\n@pysnooper.snoop(watch=('foo.bar', 'self.x[\"whatever\"]'))\n```\n\nShow snoop lines for functions that your function calls:\n\n```python\n@pysnooper.snoop(depth=2)\n```\n\n**See [Advanced Usage](https://github.com/cool-RR/PySnooper/blob/master/ADVANCED_USAGE.md) for more options.** <------\n\n\n## Installation with Pip\n\nThe best way to install **PySnooper** is with Pip:\n\n```console\n$ pip install pysnooper\n```\n\n## Other installation options\n\nConda with conda-forge channel:\n\n```console\n$ conda install -c conda-forge pysnooper\n```\n\nArch Linux:\n\n```console\n$ yay -S python-pysnooper\n```\n\nFedora Linux:\n\n```console\n$ dnf install python3-pysnooper\n```\n\n\n## Citing PySnooper\n\nIf you use PySnooper in academic work, please use this citation format:\n\n```bibtex\n@software{rachum2019pysnooper,\n    title={PySnooper: Never use print for debugging again},\n    author={Rachum, Ram and Hall, Alex and Yanokura, Iori and others},\n    year={2019},\n    month={jun},\n    publisher={PyCon Israel},\n    doi={10.5281/zenodo.10462459},\n    url={https://github.com/cool-RR/PySnooper}\n}\n```\n\n\n## License\n\nCopyright (c) 2019 Ram Rachum and collaborators, released under the MIT license.\n\n\n## Media Coverage\n\n[Hacker News thread](https://news.ycombinator.com/item?id=19717786)\nand [/r/Python Reddit thread](https://www.reddit.com/r/Python/comments/bg0ida/pysnooper_never_use_print_for_debugging_again/) (22 April 2019)\n"
  },
  {
    "path": "make_release.sh",
    "content": "#!/usr/bin/env bash\nrm -rf dist/* build/* && python setup.py sdist bdist_wheel --universal && twine upload dist/*"
  },
  {
    "path": "misc/IDE files/PySnooper.wpr",
    "content": "#!wing\n#!version=11.0\n##################################################################\n# Wing project file                                              #\n##################################################################\n[project attributes]\nproj.directory-list = [{'dirloc': loc('../..'),\n                        'excludes': ['dist',\n                                     '.tox',\n                                     'htmlcov',\n                                     'build',\n                                     '.ipynb_checkpoints',\n                                     'PySnooper.egg-info'],\n                        'filter': '*',\n                        'include_hidden': False,\n                        'recursive': True,\n                        'watch_for_changes': True}]\nproj.file-type = 'shared'\nproj.home-dir = loc('../..')\nproj.launch-config = {loc('../../../../../../Program Files/Python37/Scripts/pasteurize-script.py'): ('project',\n        ('\"c:\\\\Users\\\\Administrator\\\\Documents\\\\Python Projects\\\\PySnooper\\\\pysnooper\" \"c:\\\\Users\\\\Administrator\\\\Documents\\\\Python Projects\\\\PySnooper\\\\tests\"',\n         '')),\n                      loc('../../../../../Dropbox/Scripts and shortcuts/_simplify3d_add_m600.py'): ('project',\n        ('\"C:\\\\Users\\\\Administrator\\\\Dropbox\\\\Desktop\\\\foo.gcode\"',\n         ''))}\ntesting.auto-test-file-specs = (('regex',\n                                 'pysnooper/tests.*/test[^./]*.py.?$'),)\ntesting.test-framework = {None: ':internal pytest'}\n"
  },
  {
    "path": "misc/generate_authors.py",
    "content": "#!/usr/bin/env python\n# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\n\n'''\nGenerate an AUTHORS file for your Git repo.\n\nThis will list the authors by chronological order, from their first\ncontribution.\n\nYou probably want to run it this way:\n\n    ./generate_authors > AUTHORS\n\n'''\n\n\nimport subprocess\nimport sys\n\n# This is used for people who show up more than once:\ndeny_list = frozenset((\n    'Lumir Balhar',\n))\n\n\ndef drop_recurrences(iterable):\n    s = set()\n    for item in iterable:\n        if item not in s:\n            s.add(item)\n            yield item\n\n\ndef iterate_authors_by_chronological_order(branch):\n    log_call = subprocess.run(\n        (\n            'git', 'log', branch, '--encoding=utf-8', '--full-history',\n            '--reverse', '--format=format:%at;%an;%ae'\n        ),\n        stdout=subprocess.PIPE, stderr=subprocess.PIPE,\n    )\n    log_lines = log_call.stdout.decode('utf-8').split('\\n')\n    \n    authors = tuple(line.strip().split(\";\")[1] for line in log_lines)\n    authors = (author for author in authors if author not in deny_list)\n    return drop_recurrences(authors)\n\n\ndef print_authors(branch):\n    for author in iterate_authors_by_chronological_order(branch):\n        sys.stdout.buffer.write(author.encode())\n        sys.stdout.buffer.write(b'\\n')\n\n\nif __name__ == '__main__':\n    try:\n        branch = sys.argv[1]\n    except IndexError:\n        branch = 'master'\n    print_authors(branch)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools\"]\nbuild-backend = \"setuptools.build_meta\"\n"
  },
  {
    "path": "pysnooper/__init__.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n'''\nPySnooper - Never use print for debugging again\n\nUsage:\n\n    import pysnooper\n\n    @pysnooper.snoop()\n    def your_function(x):\n        ...\n\nA log will be written to stderr showing the lines executed and variables\nchanged in the decorated function.\n\nFor more information, see https://github.com/cool-RR/PySnooper\n'''\n\nfrom .tracer import Tracer as snoop\nfrom .variables import Attrs, Exploding, Indices, Keys\nimport collections\n\n__VersionInfo = collections.namedtuple('VersionInfo',\n                                       ('major', 'minor', 'micro'))\n\n__version__ = '1.2.3'\n__version_info__ = __VersionInfo(*(map(int, __version__.split('.'))))\n\ndel collections, __VersionInfo # Avoid polluting the namespace\n"
  },
  {
    "path": "pysnooper/pycompat.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n'''Python 2/3 compatibility'''\n\nimport abc\nimport os\nimport inspect\nimport sys\nimport datetime as datetime_module\n\nPY3 = (sys.version_info[0] == 3)\nPY2 = not PY3\n\nif hasattr(abc, 'ABC'):\n    ABC = abc.ABC\nelse:\n    class ABC(object):\n        \"\"\"Helper class that provides a standard way to create an ABC using\n        inheritance.\n        \"\"\"\n        __metaclass__ = abc.ABCMeta\n        __slots__ = ()\n\n\nif hasattr(os, 'PathLike'):\n    PathLike = os.PathLike\nelse:\n    class PathLike(ABC):\n        \"\"\"Abstract base class for implementing the file system path protocol.\"\"\"\n\n        @abc.abstractmethod\n        def __fspath__(self):\n            \"\"\"Return the file system path representation of the object.\"\"\"\n            raise NotImplementedError\n\n        @classmethod\n        def __subclasshook__(cls, subclass):\n            return (\n                hasattr(subclass, '__fspath__') or\n                # Make a concession for older `pathlib` versions:g\n                (hasattr(subclass, 'open') and\n                 'path' in subclass.__name__.lower())\n            )\n\n\ntry:\n    iscoroutinefunction = inspect.iscoroutinefunction\nexcept AttributeError:\n    iscoroutinefunction = lambda whatever: False # Lolz\n\ntry:\n    isasyncgenfunction = inspect.isasyncgenfunction\nexcept AttributeError:\n    isasyncgenfunction = lambda whatever: False # Lolz\n\n\nif PY3:\n    string_types = (str,)\n    text_type = str\n    binary_type = bytes\nelse:\n    string_types = (basestring,)\n    text_type = unicode\n    binary_type = str\n\n\ntry:\n    from collections import abc as collections_abc\nexcept ImportError: # Python 2.7\n    import collections as collections_abc\n\nif sys.version_info[:2] >= (3, 6):\n    time_isoformat = datetime_module.time.isoformat\nelse:\n    def time_isoformat(time, timespec='microseconds'):\n        assert isinstance(time, datetime_module.time)\n        if timespec != 'microseconds':\n            raise NotImplementedError\n        result = '{:02d}:{:02d}:{:02d}.{:06d}'.format(\n            time.hour, time.minute, time.second, time.microsecond\n        )\n        assert len(result) == 15\n        return result\n\n\ndef timedelta_format(timedelta):\n    time = (datetime_module.datetime.min + timedelta).time()\n    return time_isoformat(time, timespec='microseconds')\n\ndef timedelta_parse(s):\n    hours, minutes, seconds, microseconds = map(\n        int,\n        s.replace('.', ':').split(':')\n    )\n    return datetime_module.timedelta(hours=hours, minutes=minutes,\n                                     seconds=seconds,\n                                     microseconds=microseconds)\n\n"
  },
  {
    "path": "pysnooper/tracer.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\nimport functools\nimport inspect\nimport opcode\nimport os\nimport sys\nimport re\nimport collections\nimport datetime as datetime_module\nimport itertools\nimport threading\nimport traceback\n\nfrom .variables import CommonVariable, Exploding, BaseVariable\nfrom . import utils, pycompat\nif pycompat.PY2:\n    from io import open\n\n\nipython_filename_pattern = re.compile('^<ipython-input-([0-9]+)-.*>$')\nansible_filename_pattern = re.compile(r'^(.+\\.zip)[/|\\\\](ansible[/|\\\\]modules[/|\\\\].+\\.py)$')\nipykernel_filename_pattern = re.compile(r'^/var/folders/.*/ipykernel_[0-9]+/[0-9]+.py$')\nRETURN_OPCODES = {\n    'RETURN_GENERATOR', 'RETURN_VALUE', 'RETURN_CONST',\n    'INSTRUMENTED_RETURN_GENERATOR', 'INSTRUMENTED_RETURN_VALUE',\n    'INSTRUMENTED_RETURN_CONST', 'YIELD_VALUE', 'INSTRUMENTED_YIELD_VALUE'\n}\n\n\ndef get_local_reprs(frame, watch=(), custom_repr=(), max_length=None, normalize=False):\n    code = frame.f_code\n    vars_order = (code.co_varnames + code.co_cellvars + code.co_freevars +\n                  tuple(frame.f_locals.keys()))\n\n    result_items = [(key, utils.get_shortish_repr(value, custom_repr,\n                                                  max_length, normalize))\n                    for key, value in frame.f_locals.items()]\n    result_items.sort(key=lambda key_value: vars_order.index(key_value[0]))\n    result = collections.OrderedDict(result_items)\n\n    for variable in watch:\n        result.update(sorted(variable.items(frame, normalize)))\n    return result\n\n\nclass UnavailableSource(object):\n    def __getitem__(self, i):\n        return u'SOURCE IS UNAVAILABLE'\n\n\nsource_and_path_cache = {}\n\n\ndef get_path_and_source_from_frame(frame):\n    globs = frame.f_globals or {}\n    module_name = globs.get('__name__')\n    file_name = frame.f_code.co_filename\n    cache_key = (module_name, file_name)\n    try:\n        return source_and_path_cache[cache_key]\n    except KeyError:\n        pass\n    loader = globs.get('__loader__')\n\n    source = None\n    if hasattr(loader, 'get_source'):\n        try:\n            source = loader.get_source(module_name)\n        except ImportError:\n            pass\n        if source is not None:\n            source = source.splitlines()\n    if source is None:\n        ipython_filename_match = ipython_filename_pattern.match(file_name)\n        ansible_filename_match = ansible_filename_pattern.match(file_name)\n        ipykernel_filename_match = ipykernel_filename_pattern.match(file_name)\n        if ipykernel_filename_match:\n            try:\n                import linecache\n                _, _, source, _ = linecache.cache.get(file_name)\n                source = [line.rstrip() for line in source] # remove '\\n' at the end\n            except Exception:\n                pass\n        elif ipython_filename_match:\n            entry_number = int(ipython_filename_match.group(1))\n            try:\n                import IPython\n                ipython_shell = IPython.get_ipython()\n                ((_, _, source_chunk),) = ipython_shell.history_manager. \\\n                                  get_range(0, entry_number, entry_number + 1)\n                source = source_chunk.splitlines()\n            except Exception:\n                pass\n        elif ansible_filename_match:\n            try:\n                import zipfile\n                archive_file = zipfile.ZipFile(ansible_filename_match.group(1), 'r')\n                source = archive_file.read(ansible_filename_match.group(2).replace('\\\\', '/')).splitlines()\n            except Exception:\n                pass\n        else:\n            try:\n                with open(file_name, 'rb') as fp:\n                    source = fp.read().splitlines()\n            except utils.file_reading_errors:\n                pass\n    if not source:\n        # We used to check `if source is None` but I found a rare bug where it\n        # was empty, but not `None`, so now we check `if not source`.\n        source = UnavailableSource()\n\n    # If we just read the source from a file, or if the loader did not\n    # apply tokenize.detect_encoding to decode the source into a\n    # string, then we should do that ourselves.\n    if isinstance(source[0], bytes):\n        encoding = 'utf-8'\n        for line in source[:2]:\n            # File coding may be specified. Match pattern from PEP-263\n            # (https://www.python.org/dev/peps/pep-0263/)\n            match = re.search(br'coding[:=]\\s*([-\\w.]+)', line)\n            if match:\n                encoding = match.group(1).decode('ascii')\n                break\n        source = [pycompat.text_type(sline, encoding, 'replace') for sline in\n                  source]\n\n    result = (file_name, source)\n    source_and_path_cache[cache_key] = result\n    return result\n\n\ndef get_write_function(output, overwrite):\n    is_path = isinstance(output, (pycompat.PathLike, str))\n    if overwrite and not is_path:\n        raise Exception('`overwrite=True` can only be used when writing '\n                        'content to file.')\n    if output is None:\n        def write(s):\n            stderr = sys.stderr\n            try:\n                stderr.write(s)\n            except UnicodeEncodeError:\n                # God damn Python 2\n                stderr.write(utils.shitcode(s))\n    elif is_path:\n        return FileWriter(output, overwrite).write\n    elif callable(output):\n        write = output\n    else:\n        assert isinstance(output, utils.WritableStream)\n\n        def write(s):\n            output.write(s)\n    return write\n\n\nclass FileWriter(object):\n    def __init__(self, path, overwrite):\n        self.path = pycompat.text_type(path)\n        self.overwrite = overwrite\n\n    def write(self, s):\n        with open(self.path, 'w' if self.overwrite else 'a',\n                  encoding='utf-8') as output_file:\n            output_file.write(s)\n        self.overwrite = False\n\n\nthread_global = threading.local()\nDISABLED = bool(os.getenv('PYSNOOPER_DISABLED', ''))\n\nclass Tracer:\n    '''\n    Snoop on the function, writing everything it's doing to stderr.\n\n    This is useful for debugging.\n\n    When you decorate a function with `@pysnooper.snoop()`\n    or wrap a block of code in `with pysnooper.snoop():`, you'll get a log of\n    every line that ran in the function and a play-by-play of every local\n    variable that changed.\n\n    If stderr is not easily accessible for you, you can redirect the output to\n    a file::\n\n        @pysnooper.snoop('/my/log/file.log')\n\n    See values of some expressions that aren't local variables::\n\n        @pysnooper.snoop(watch=('foo.bar', 'self.x[\"whatever\"]'))\n\n    Expand values to see all their attributes or items of lists/dictionaries:\n\n        @pysnooper.snoop(watch_explode=('foo', 'self'))\n\n    (see Advanced Usage in the README for more control)\n\n    Show snoop lines for functions that your function calls::\n\n        @pysnooper.snoop(depth=2)\n\n    Start all snoop lines with a prefix, to grep for them easily::\n\n        @pysnooper.snoop(prefix='ZZZ ')\n\n    On multi-threaded apps identify which thread are snooped in output::\n\n        @pysnooper.snoop(thread_info=True)\n\n    Customize how values are represented as strings::\n\n        @pysnooper.snoop(custom_repr=((type1, custom_repr_func1),\n                         (condition2, custom_repr_func2), ...))\n\n    Variables and exceptions get truncated to 100 characters by default. You\n    can customize that:\n\n        @pysnooper.snoop(max_variable_length=200)\n\n    You can also use `max_variable_length=None` to never truncate them.\n\n    Show timestamps relative to start time rather than wall time::\n\n        @pysnooper.snoop(relative_time=True)\n\n    The output is colored for easy viewing by default, except on Windows\n    (but can be enabled by setting `color=True`).\n\n    Disable colors like so:\n\n        @pysnooper.snoop(color=False)\n\n    '''\n    def __init__(self, output=None, watch=(), watch_explode=(), depth=1,\n                 prefix='', overwrite=False, thread_info=False, custom_repr=(),\n                 max_variable_length=100, normalize=False, relative_time=False,\n                 color=sys.platform in ('linux', 'linux2', 'cygwin', 'darwin')):\n        self._write = get_write_function(output, overwrite)\n\n        self.watch = [\n            v if isinstance(v, BaseVariable) else CommonVariable(v)\n            for v in utils.ensure_tuple(watch)\n         ] + [\n             v if isinstance(v, BaseVariable) else Exploding(v)\n             for v in utils.ensure_tuple(watch_explode)\n        ]\n        self.frame_to_local_reprs = {}\n        self.start_times = {}\n        self.depth = depth\n        self.prefix = prefix\n        self.thread_info = thread_info\n        self.thread_info_padding = 0\n        assert self.depth >= 1\n        self.target_codes = set()\n        self.target_frames = set()\n        self.thread_local = threading.local()\n        if len(custom_repr) == 2 and not all(isinstance(x,\n                      pycompat.collections_abc.Iterable) for x in custom_repr):\n            custom_repr = (custom_repr,)\n        self.custom_repr = custom_repr\n        self.last_source_path = None\n        self.max_variable_length = max_variable_length\n        self.normalize = normalize\n        self.relative_time = relative_time\n        self.color = color and (output is None)\n\n        if self.color:\n            self._FOREGROUND_BLUE = '\\x1b[34m'\n            self._FOREGROUND_CYAN = '\\x1b[36m'\n            self._FOREGROUND_GREEN = '\\x1b[32m'\n            self._FOREGROUND_MAGENTA = '\\x1b[35m'\n            self._FOREGROUND_RED = '\\x1b[31m'\n            self._FOREGROUND_RESET = '\\x1b[39m'\n            self._FOREGROUND_YELLOW = '\\x1b[33m'\n            self._STYLE_BRIGHT = '\\x1b[1m'\n            self._STYLE_DIM = '\\x1b[2m'\n            self._STYLE_NORMAL = '\\x1b[22m'\n            self._STYLE_RESET_ALL = '\\x1b[0m'\n        else:\n            self._FOREGROUND_BLUE = ''\n            self._FOREGROUND_CYAN = ''\n            self._FOREGROUND_GREEN = ''\n            self._FOREGROUND_MAGENTA = ''\n            self._FOREGROUND_RED = ''\n            self._FOREGROUND_RESET = ''\n            self._FOREGROUND_YELLOW = ''\n            self._STYLE_BRIGHT = ''\n            self._STYLE_DIM = ''\n            self._STYLE_NORMAL = ''\n            self._STYLE_RESET_ALL = ''\n\n    def __call__(self, function_or_class):\n        if DISABLED:\n            return function_or_class\n\n        if inspect.isclass(function_or_class):\n            return self._wrap_class(function_or_class)\n        else:\n            return self._wrap_function(function_or_class)\n\n    def _wrap_class(self, cls):\n        for attr_name, attr in cls.__dict__.items():\n            # Coroutines are functions, but snooping them is not supported\n            # at the moment\n            if pycompat.iscoroutinefunction(attr):\n                continue\n\n            if inspect.isfunction(attr):\n                setattr(cls, attr_name, self._wrap_function(attr))\n        return cls\n\n    def _wrap_function(self, function):\n        self.target_codes.add(function.__code__)\n\n        @functools.wraps(function)\n        def simple_wrapper(*args, **kwargs):\n            with self:\n                return function(*args, **kwargs)\n\n        @functools.wraps(function)\n        def generator_wrapper(*args, **kwargs):\n            gen = function(*args, **kwargs)\n            method, incoming = gen.send, None\n            while True:\n                with self:\n                    try:\n                        outgoing = method(incoming)\n                    except StopIteration:\n                        return\n                try:\n                    method, incoming = gen.send, (yield outgoing)\n                except Exception as e:\n                    method, incoming = gen.throw, e\n\n        if pycompat.iscoroutinefunction(function):\n            raise NotImplementedError\n        if pycompat.isasyncgenfunction(function):\n            raise NotImplementedError\n        elif inspect.isgeneratorfunction(function):\n            return generator_wrapper\n        else:\n            return simple_wrapper\n\n    def write(self, s):\n        s = u'{self.prefix}{s}\\n'.format(**locals())\n        self._write(s)\n\n    def __enter__(self):\n        if DISABLED:\n            return\n        thread_global.__dict__.setdefault('depth', -1)\n        calling_frame = inspect.currentframe().f_back\n        if not self._is_internal_frame(calling_frame):\n            calling_frame.f_trace = self.trace\n            self.target_frames.add(calling_frame)\n\n        stack = self.thread_local.__dict__.setdefault(\n            'original_trace_functions', []\n        )\n        stack.append(sys.gettrace())\n        self.start_times[calling_frame] = datetime_module.datetime.now()\n        sys.settrace(self.trace)\n\n    def __exit__(self, exc_type, exc_value, exc_traceback):\n        if DISABLED:\n            return\n        stack = self.thread_local.original_trace_functions\n        sys.settrace(stack.pop())\n        calling_frame = inspect.currentframe().f_back\n        self.target_frames.discard(calling_frame)\n        self.frame_to_local_reprs.pop(calling_frame, None)\n\n        ### Writing elapsed time: #############################################\n        #                                                                     #\n        _FOREGROUND_YELLOW = self._FOREGROUND_YELLOW\n        _STYLE_DIM = self._STYLE_DIM\n        _STYLE_NORMAL = self._STYLE_NORMAL\n        _STYLE_RESET_ALL = self._STYLE_RESET_ALL\n\n        start_time = self.start_times.pop(calling_frame)\n        duration = datetime_module.datetime.now() - start_time\n        elapsed_time_string = pycompat.timedelta_format(duration)\n        indent = ' ' * 4 * (thread_global.depth + 1)\n        self.write(\n            '{indent}{_FOREGROUND_YELLOW}{_STYLE_DIM}'\n            'Elapsed time: {_STYLE_NORMAL}{elapsed_time_string}'\n            '{_STYLE_RESET_ALL}'.format(**locals())\n        )\n        #                                                                     #\n        ### Finished writing elapsed time. ####################################\n\n    def _is_internal_frame(self, frame):\n        return frame.f_code.co_filename == Tracer.__enter__.__code__.co_filename\n\n    def set_thread_info_padding(self, thread_info):\n        current_thread_len = len(thread_info)\n        self.thread_info_padding = max(self.thread_info_padding,\n                                       current_thread_len)\n        return thread_info.ljust(self.thread_info_padding)\n\n    def trace(self, frame, event, arg):\n\n        ### Checking whether we should trace this line: #######################\n        #                                                                     #\n        # We should trace this line either if it's in the decorated function,\n        # or the user asked to go a few levels deeper and we're within that\n        # number of levels deeper.\n\n        if not (frame.f_code in self.target_codes or frame in self.target_frames):\n            if self.depth == 1:\n                # We did the most common and quickest check above, because the\n                # trace function runs so incredibly often, therefore it's\n                # crucial to hyper-optimize it for the common case.\n                return None\n            elif self._is_internal_frame(frame):\n                return None\n            else:\n                _frame_candidate = frame\n                for i in range(1, self.depth):\n                    _frame_candidate = _frame_candidate.f_back\n                    if _frame_candidate is None:\n                        return None\n                    elif _frame_candidate.f_code in self.target_codes or _frame_candidate in self.target_frames:\n                        break\n                else:\n                    return None\n\n        #                                                                     #\n        ### Finished checking whether we should trace this line. ##############\n\n        if event == 'call':\n            thread_global.depth += 1\n        indent = ' ' * 4 * thread_global.depth\n\n        _FOREGROUND_BLUE = self._FOREGROUND_BLUE\n        _FOREGROUND_CYAN = self._FOREGROUND_CYAN\n        _FOREGROUND_GREEN = self._FOREGROUND_GREEN\n        _FOREGROUND_MAGENTA = self._FOREGROUND_MAGENTA\n        _FOREGROUND_RED = self._FOREGROUND_RED\n        _FOREGROUND_RESET = self._FOREGROUND_RESET\n        _FOREGROUND_YELLOW = self._FOREGROUND_YELLOW\n        _STYLE_BRIGHT = self._STYLE_BRIGHT\n        _STYLE_DIM = self._STYLE_DIM\n        _STYLE_NORMAL = self._STYLE_NORMAL\n        _STYLE_RESET_ALL = self._STYLE_RESET_ALL\n\n        ### Making timestamp: #################################################\n        #                                                                     #\n        if self.normalize:\n            timestamp = ' ' * 15\n        elif self.relative_time:\n            try:\n                start_time = self.start_times[frame]\n            except KeyError:\n                start_time = self.start_times[frame] = \\\n                                                 datetime_module.datetime.now()\n            duration = datetime_module.datetime.now() - start_time\n            timestamp = pycompat.timedelta_format(duration)\n        else:\n            timestamp = pycompat.time_isoformat(\n                datetime_module.datetime.now().time(),\n                timespec='microseconds'\n            )\n        #                                                                     #\n        ### Finished making timestamp. ########################################\n\n        line_no = frame.f_lineno\n        source_path, source = get_path_and_source_from_frame(frame)\n        source_path = source_path if not self.normalize else os.path.basename(source_path)\n        if self.last_source_path != source_path:\n            self.write(u'{_FOREGROUND_YELLOW}{_STYLE_DIM}{indent}Source path:... '\n                       u'{_STYLE_NORMAL}{source_path}'\n                       u'{_STYLE_RESET_ALL}'.format(**locals()))\n            self.last_source_path = source_path\n        source_line = source[line_no - 1]\n        thread_info = \"\"\n        if self.thread_info:\n            if self.normalize:\n                raise NotImplementedError(\"normalize is not supported with \"\n                                          \"thread_info\")\n            current_thread = threading.current_thread()\n            thread_info = \"{ident}-{name} \".format(\n                ident=current_thread.ident, name=current_thread.name)\n        thread_info = self.set_thread_info_padding(thread_info)\n\n        ### Reporting newish and modified variables: ##########################\n        #                                                                     #\n        old_local_reprs = self.frame_to_local_reprs.get(frame, {})\n        self.frame_to_local_reprs[frame] = local_reprs = \\\n                                       get_local_reprs(frame,\n                                                       watch=self.watch, custom_repr=self.custom_repr,\n                                                       max_length=self.max_variable_length,\n                                                       normalize=self.normalize,\n                                                       )\n\n        newish_string = ('Starting var:.. ' if event == 'call' else\n                                                            'New var:....... ')\n\n        for name, value_repr in local_reprs.items():\n            if name not in old_local_reprs:\n                self.write('{indent}{_FOREGROUND_GREEN}{_STYLE_DIM}'\n                           '{newish_string}{_STYLE_NORMAL}{name} = '\n                           '{value_repr}{_STYLE_RESET_ALL}'.format(**locals()))\n            elif old_local_reprs[name] != value_repr:\n                self.write('{indent}{_FOREGROUND_GREEN}{_STYLE_DIM}'\n                           'Modified var:.. {_STYLE_NORMAL}{name} = '\n                           '{value_repr}{_STYLE_RESET_ALL}'.format(**locals()))\n\n        #                                                                     #\n        ### Finished newish and modified variables. ###########################\n\n\n        ### Dealing with misplaced function definition: #######################\n        #                                                                     #\n        if event == 'call' and source_line.lstrip().startswith('@'):\n            # If a function decorator is found, skip lines until an actual\n            # function definition is found.\n            for candidate_line_no in itertools.count(line_no):\n                try:\n                    candidate_source_line = source[candidate_line_no - 1]\n                except IndexError:\n                    # End of source file reached without finding a function\n                    # definition. Fall back to original source line.\n                    break\n\n                if candidate_source_line.lstrip().startswith('def'):\n                    # Found the def line!\n                    line_no = candidate_line_no\n                    source_line = candidate_source_line\n                    break\n        #                                                                     #\n        ### Finished dealing with misplaced function definition. ##############\n\n        # If a call ends due to an exception, we still get a 'return' event\n        # with arg = None. This seems to be the only way to tell the difference\n        # https://stackoverflow.com/a/12800909/2482744\n        code_byte = frame.f_code.co_code[frame.f_lasti]\n        if not isinstance(code_byte, int):\n            code_byte = ord(code_byte)\n        ended_by_exception = (\n                event == 'return'\n                and arg is None\n                and opcode.opname[code_byte] not in RETURN_OPCODES\n        )\n\n        if ended_by_exception:\n            self.write('{_FOREGROUND_RED}{indent}Call ended by exception{_STYLE_RESET_ALL}'.\n                       format(**locals()))\n        else:\n            self.write(u'{indent}{_STYLE_DIM}{timestamp} {thread_info}{event:9} '\n                       u'{line_no:4}{_STYLE_RESET_ALL} {source_line}'.format(**locals()))\n\n        if event == 'return':\n            self.frame_to_local_reprs.pop(frame, None)\n            self.start_times.pop(frame, None)\n            thread_global.depth -= 1\n\n            if not ended_by_exception:\n                return_value_repr = utils.get_shortish_repr(arg,\n                                                            custom_repr=self.custom_repr,\n                                                            max_length=self.max_variable_length,\n                                                            normalize=self.normalize,\n                                                            )\n                self.write('{indent}{_FOREGROUND_CYAN}{_STYLE_DIM}'\n                           'Return value:.. {_STYLE_NORMAL}{return_value_repr}'\n                           '{_STYLE_RESET_ALL}'.\n                           format(**locals()))\n\n        if event == 'exception':\n            exception = utils.format_exception(*arg[:2])\n            if self.max_variable_length:\n                exception = utils.truncate(exception, self.max_variable_length)\n            self.write('{indent}{_FOREGROUND_RED}Exception:..... '\n                       '{_STYLE_BRIGHT}{exception}'\n                       '{_STYLE_RESET_ALL}'.format(**locals()))\n\n        return self.trace\n"
  },
  {
    "path": "pysnooper/utils.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\nimport abc\nimport re\nimport traceback\n\nimport sys\nfrom .pycompat import ABC, string_types, collections_abc\n\ndef _check_methods(C, *methods):\n    mro = C.__mro__\n    for method in methods:\n        for B in mro:\n            if method in B.__dict__:\n                if B.__dict__[method] is None:\n                    return NotImplemented\n                break\n        else:\n            return NotImplemented\n    return True\n\n\nclass WritableStream(ABC):\n    @abc.abstractmethod\n    def write(self, s):\n        pass\n\n    @classmethod\n    def __subclasshook__(cls, C):\n        if cls is WritableStream:\n            return _check_methods(C, 'write')\n        return NotImplemented\n\n\n\nfile_reading_errors = (\n    IOError,\n    OSError,\n    ValueError # IronPython weirdness.\n)\n\n\n\ndef shitcode(s):\n    return ''.join(\n        (c if (0 < ord(c) < 256) else '?') for c in s\n    )\n\n\ndef get_repr_function(item, custom_repr):\n    for condition, action in custom_repr:\n        if isinstance(condition, type):\n            condition = lambda x, y=condition: isinstance(x, y)\n        if condition(item):\n            return action\n    return repr\n\n\nDEFAULT_REPR_RE = re.compile(r' at 0x[a-f0-9A-F]{4,}')\n\n\ndef normalize_repr(item_repr):\n    \"\"\"Remove memory address (0x...) from a default python repr\"\"\"\n    return DEFAULT_REPR_RE.sub('', item_repr)\n\n\ndef get_shortish_repr(item, custom_repr=(), max_length=None, normalize=False):\n    repr_function = get_repr_function(item, custom_repr)\n    try:\n        r = repr_function(item)\n    except Exception:\n        r = 'REPR FAILED'\n    r = r.replace('\\r', '').replace('\\n', '')\n    if normalize:\n        r = normalize_repr(r)\n    if max_length:\n        r = truncate(r, max_length)\n    return r\n\n\ndef truncate(string, max_length):\n    if (max_length is None) or (len(string) <= max_length):\n        return string\n    else:\n        left = (max_length - 3) // 2\n        right = max_length - 3 - left\n        return u'{}...{}'.format(string[:left], string[-right:])\n\n\ndef format_exception(exc_type, exc_value):\n    try:\n        is_group = isinstance(exc_value, BaseExceptionGroup)\n    except NameError:\n        is_group = False\n    if is_group:\n        sub_types = ', '.join(type(e).__name__ for e in exc_value.exceptions)\n        message = exc_value.args[0] if exc_value.args else ''\n        return u\"{}: '{}' ({} sub-exceptions: {})\".format(\n            exc_type.__name__, message,\n            len(exc_value.exceptions), sub_types,\n        )\n    return u'\\n'.join(traceback.format_exception_only(exc_type, exc_value)).strip()\n\n\ndef ensure_tuple(x):\n    if isinstance(x, collections_abc.Iterable) and \\\n                                               not isinstance(x, string_types):\n        return tuple(x)\n    else:\n        return (x,)\n\n\n\n"
  },
  {
    "path": "pysnooper/variables.py",
    "content": "import itertools\nimport abc\ntry:\n    from collections.abc import Mapping, Sequence\nexcept ImportError:\n    from collections import Mapping, Sequence\nfrom copy import deepcopy\n\nfrom . import utils\nfrom . import pycompat\n\n\ndef needs_parentheses(source):\n    def code(s):\n        return compile(s, '<variable>', 'eval').co_code\n\n    return code('{}.x'.format(source)) != code('({}).x'.format(source))\n\n\nclass BaseVariable(pycompat.ABC):\n    def __init__(self, source, exclude=()):\n        self.source = source\n        self.exclude = utils.ensure_tuple(exclude)\n        self.code = compile(source, '<variable>', 'eval')\n        if needs_parentheses(source):\n            self.unambiguous_source = '({})'.format(source)\n        else:\n            self.unambiguous_source = source\n\n    def items(self, frame, normalize=False):\n        try:\n            main_value = eval(self.code, frame.f_globals or {}, frame.f_locals)\n        except Exception:\n            return ()\n        return self._items(main_value, normalize)\n\n    @abc.abstractmethod\n    def _items(self, key, normalize=False):\n        raise NotImplementedError\n\n    @property\n    def _fingerprint(self):\n        return (type(self), self.source, self.exclude)\n\n    def __hash__(self):\n        return hash(self._fingerprint)\n\n    def __eq__(self, other):\n        return (isinstance(other, BaseVariable) and\n                                       self._fingerprint == other._fingerprint)\n\n\nclass CommonVariable(BaseVariable):\n    def _items(self, main_value, normalize=False):\n        result = [(self.source, utils.get_shortish_repr(main_value, normalize=normalize))]\n        for key in self._safe_keys(main_value):\n            try:\n                if key in self.exclude:\n                    continue\n                value = self._get_value(main_value, key)\n            except Exception:\n                continue\n            result.append((\n                '{}{}'.format(self.unambiguous_source, self._format_key(key)),\n                utils.get_shortish_repr(value)\n            ))\n        return result\n\n    def _safe_keys(self, main_value):\n        try:\n            for key in self._keys(main_value):\n                yield key\n        except Exception:\n            pass\n\n    def _keys(self, main_value):\n        return ()\n\n    def _format_key(self, key):\n        raise NotImplementedError\n\n    def _get_value(self, main_value, key):\n        raise NotImplementedError\n\n\nclass Attrs(CommonVariable):\n    def _keys(self, main_value):\n        return itertools.chain(\n            getattr(main_value, '__dict__', ()),\n            getattr(main_value, '__slots__', ())\n        )\n\n    def _format_key(self, key):\n        return '.' + key\n\n    def _get_value(self, main_value, key):\n        return getattr(main_value, key)\n\n\nclass Keys(CommonVariable):\n    def _keys(self, main_value):\n        return main_value.keys()\n\n    def _format_key(self, key):\n        return '[{}]'.format(utils.get_shortish_repr(key))\n\n    def _get_value(self, main_value, key):\n        return main_value[key]\n\n\nclass Indices(Keys):\n    _slice = slice(None)\n\n    def _keys(self, main_value):\n        return range(len(main_value))[self._slice]\n\n    def __getitem__(self, item):\n        assert isinstance(item, slice)\n        result = deepcopy(self)\n        result._slice = item\n        return result\n\n\nclass Exploding(BaseVariable):\n    def _items(self, main_value, normalize=False):\n        if isinstance(main_value, Mapping):\n            cls = Keys\n        elif isinstance(main_value, Sequence):\n            cls = Indices\n        else:\n            cls = Attrs\n\n        return cls(self.source, self.exclude)._items(main_value, normalize)\n"
  },
  {
    "path": "requirements.in",
    "content": "# That's right baby! No dependencies!"
  },
  {
    "path": "requirements.txt",
    "content": "#\n# This file is autogenerated by pip-compile\n# To update, run:\n#\n#    pip-compile --output-file requirements.txt requirements.in\n#\n"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\nname = PySnooper\nversion = attr: pysnooper.__version__\nauthor = Ram Rachum\nauthor_email = ram@rachum.com\ndescription = A poor man's debugger for Python.\nurl = https://github.com/cool-RR/PySnooper\nlong_description = file: README.md\nlong_description_content_type = text/markdown\nclassifiers =\n    Environment :: Console\n    Intended Audience :: Developers\n    Programming Language :: Python :: 2.7\n    Programming Language :: Python :: 3.4\n    Programming Language :: Python :: 3.5\n    Programming Language :: Python :: 3.6\n    Programming Language :: Python :: 3.7\n    Programming Language :: Python :: 3.8\n    Programming Language :: Python :: 3.9\n    Programming Language :: Python :: 3.10\n    Programming Language :: Python :: 3.11\n    Programming Language :: Python :: 3.12\n    Programming Language :: Python :: Implementation :: CPython\n    Programming Language :: Python :: Implementation :: PyPy\n    License :: OSI Approved :: MIT License\n    Operating System :: OS Independent\n    Topic :: Software Development :: Debuggers\n\n[options]\npackages = find:\ninstall_requires = file: requirements.in\n\n[options.packages.find]\nexclude = tests*\n\n[options.extras_require]\ntests = pytest\n"
  },
  {
    "path": "setup.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\nimport setuptools\nimport re\n\n\ndef read_file(filename):\n    with open(filename) as file:\n        return file.read()\n\nversion = re.search(\"__version__ = '([0-9.]*)'\",\n                    read_file('pysnooper/__init__.py')).group(1)\n\nsetuptools.setup(\n    name='PySnooper',\n    version=version,\n    author='Ram Rachum',\n    author_email='ram@rachum.com',\n    description=\"A poor man's debugger for Python.\",\n    long_description=read_file('README.md'),\n    long_description_content_type='text/markdown',\n    url='https://github.com/cool-RR/PySnooper',\n    packages=setuptools.find_packages(exclude=['tests*']),\n    install_requires=read_file('requirements.in'),\n    extras_require={\n        'tests': {\n            'pytest',\n        },\n    },\n    classifiers=[\n        'Environment :: Console',\n        'Intended Audience :: Developers',\n        'Programming Language :: Python :: 2.7',\n        'Programming Language :: Python :: 3.4',\n        'Programming Language :: Python :: 3.5',\n        'Programming Language :: Python :: 3.6',\n        'Programming Language :: Python :: 3.7',\n        'Programming Language :: Python :: 3.8',\n        'Programming Language :: Python :: 3.9',\n        'Programming Language :: Python :: 3.10',\n        'Programming Language :: Python :: 3.11',\n        'Programming Language :: Python :: 3.12',\n        'Programming Language :: Python :: 3.13',\n        'Programming Language :: Python :: 3.14',\n        'Programming Language :: Python :: 3.15',\n        'Programming Language :: Python :: Implementation :: CPython',\n        'Programming Language :: Python :: Implementation :: PyPy',\n        'License :: OSI Approved :: MIT License',\n        'Operating System :: OS Independent',\n        'Topic :: Software Development :: Debuggers',\n    ],\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "import pytest\n\npytest.register_assert_rewrite('tests.utils')\n"
  },
  {
    "path": "tests/mini_toolbox/__init__.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\nimport tempfile\nimport shutil\nimport io\nimport sys\nfrom . import pathlib\nfrom . import contextlib\n\n\n\n@contextlib.contextmanager\ndef BlankContextManager():\n    yield\n\n@contextlib.contextmanager\ndef create_temp_folder(prefix=tempfile.template, suffix='',\n                       parent_folder=None, chmod=None):\n    '''\n    Context manager that creates a temporary folder and deletes it after usage.\n\n    After the suite finishes, the temporary folder and all its files and\n    subfolders will be deleted.\n\n    Example:\n\n        with create_temp_folder() as temp_folder:\n\n            # We have a temporary folder!\n            assert temp_folder.is_dir()\n\n            # We can create files in it:\n            (temp_folder / 'my_file').open('w')\n\n        # The suite is finished, now it's all cleaned:\n        assert not temp_folder.exists()\n\n    Use the `prefix` and `suffix` string arguments to dictate a prefix and/or a\n    suffix to the temporary folder's name in the filesystem.\n\n    If you'd like to set the permissions of the temporary folder, pass them to\n    the optional `chmod` argument, like this:\n\n        create_temp_folder(chmod=0o550)\n\n    '''\n    temp_folder = pathlib.Path(tempfile.mkdtemp(prefix=prefix, suffix=suffix,\n                                                dir=parent_folder))\n    try:\n        if chmod is not None:\n            temp_folder.chmod(chmod)\n        yield temp_folder\n    finally:\n        shutil.rmtree(str(temp_folder))\n\n\nclass NotInDict:\n    '''Object signifying that the key was not found in the dict.'''\n\n\nclass TempValueSetter(object):\n    '''\n    Context manager for temporarily setting a value to a variable.\n\n    The value is set to the variable before the suite starts, and gets reset\n    back to the old value after the suite finishes.\n    '''\n\n    def __init__(self, variable, value, assert_no_fiddling=True):\n        '''\n        Construct the `TempValueSetter`.\n\n        `variable` may be either an `(object, attribute_string)`, a `(dict,\n        key)` pair, or a `(getter, setter)` pair.\n\n        `value` is the temporary value to set to the variable.\n        '''\n\n        self.assert_no_fiddling = assert_no_fiddling\n\n\n        #######################################################################\n        # We let the user input either an `(object, attribute_string)`, a\n        # `(dict, key)` pair, or a `(getter, setter)` pair. So now it's our job\n        # to inspect `variable` and figure out which one of these options the\n        # user chose, and then obtain from that a `(getter, setter)` pair that\n        # we could use.\n\n        bad_input_exception = Exception(\n            '`variable` must be either an `(object, attribute_string)` pair, '\n            'a `(dict, key)` pair, or a `(getter, setter)` pair.'\n        )\n\n        try:\n            first, second = variable\n        except Exception:\n            raise bad_input_exception\n        if hasattr(first, '__getitem__') and hasattr(first, 'get') and \\\n           hasattr(first, '__setitem__') and hasattr(first, '__delitem__'):\n            # `first` is a dictoid; so we were probably handed a `(dict, key)`\n            # pair.\n            self.getter = lambda: first.get(second, NotInDict)\n            self.setter = lambda value: (first.__setitem__(second, value) if\n                                         value is not NotInDict else\n                                         first.__delitem__(second))\n            ### Finished handling the `(dict, key)` case. ###\n\n        elif callable(second):\n            # `second` is a callable; so we were probably handed a `(getter,\n            # setter)` pair.\n            if not callable(first):\n                raise bad_input_exception\n            self.getter, self.setter = first, second\n            ### Finished handling the `(getter, setter)` case. ###\n        else:\n            # All that's left is the `(object, attribute_string)` case.\n            if not isinstance(second, str):\n                raise bad_input_exception\n\n            parent, attribute_name = first, second\n            self.getter = lambda: getattr(parent, attribute_name)\n            self.setter = lambda value: setattr(parent, attribute_name, value)\n            ### Finished handling the `(object, attribute_string)` case. ###\n\n        #\n        #\n        ### Finished obtaining a `(getter, setter)` pair from `variable`. #####\n\n\n        self.getter = self.getter\n        '''Getter for getting the current value of the variable.'''\n\n        self.setter = self.setter\n        '''Setter for Setting the the variable's value.'''\n\n        self.value = value\n        '''The value to temporarily set to the variable.'''\n\n        self.active = False\n\n\n    def __enter__(self):\n\n        self.active = True\n\n        self.old_value = self.getter()\n        '''The old value of the variable, before entering the suite.'''\n\n        self.setter(self.value)\n\n        # In `__exit__` we'll want to check if anyone changed the value of the\n        # variable in the suite, which is unallowed. But we can't compare to\n        # `.value`, because sometimes when you set a value to a variable, some\n        # mechanism modifies that value for various reasons, resulting in a\n        # supposedly equivalent, but not identical, value. For example this\n        # happens when you set the current working directory on Mac OS.\n        #\n        # So here we record the value right after setting, and after any\n        # possible processing the system did to it:\n        self._value_right_after_setting = self.getter()\n\n        return self\n\n\n    def __exit__(self, exc_type, exc_value, exc_traceback):\n\n        if self.assert_no_fiddling:\n            # Asserting no-one inside the suite changed our variable:\n            assert self.getter() == self._value_right_after_setting\n\n        self.setter(self.old_value)\n\n        self.active = False\n\nclass OutputCapturer(object):\n    '''\n    Context manager for catching all system output generated during suite.\n\n    Example:\n\n        with OutputCapturer() as output_capturer:\n            print('woo!')\n\n        assert output_capturer.output == 'woo!\\n'\n\n    The boolean arguments `stdout` and `stderr` determine, respectively,\n    whether the standard-output and the standard-error streams will be\n    captured.\n    '''\n    def __init__(self, stdout=True, stderr=True):\n        self.string_io = io.StringIO()\n\n        if stdout:\n            self._stdout_temp_setter = \\\n                TempValueSetter((sys, 'stdout'), self.string_io)\n        else: # not stdout\n            self._stdout_temp_setter = BlankContextManager()\n\n        if stderr:\n            self._stderr_temp_setter = \\\n                TempValueSetter((sys, 'stderr'), self.string_io)\n        else: # not stderr\n            self._stderr_temp_setter = BlankContextManager()\n\n    def __enter__(self):\n        '''Manage the `OutputCapturer`'s context.'''\n        self._stdout_temp_setter.__enter__()\n        self._stderr_temp_setter.__enter__()\n        return self\n\n    def __exit__(self, exc_type, exc_value, exc_traceback):\n        # Not doing exception swallowing anywhere here.\n        self._stderr_temp_setter.__exit__(exc_type, exc_value, exc_traceback)\n        self._stdout_temp_setter.__exit__(exc_type, exc_value, exc_traceback)\n\n    output = property(lambda self: self.string_io.getvalue(),\n                      doc='''The string of output that was captured.''')\n\n\nclass TempSysPathAdder(object):\n    '''\n    Context manager for temporarily adding paths to `sys.path`.\n\n    Removes the path(s) after suite.\n\n    Example:\n\n        with TempSysPathAdder('path/to/fubar/package'):\n            import fubar\n            fubar.do_stuff()\n\n    '''\n    def __init__(self, addition):\n        self.addition = [str(addition)]\n\n\n    def __enter__(self):\n        self.entries_not_in_sys_path = [entry for entry in self.addition if\n                                        entry not in sys.path]\n        sys.path += self.entries_not_in_sys_path\n        return self\n\n\n    def __exit__(self, *args, **kwargs):\n\n        for entry in self.entries_not_in_sys_path:\n\n            # We don't allow anyone to remove it except for us:\n            assert entry in sys.path\n\n            sys.path.remove(entry)\n\n\n"
  },
  {
    "path": "tests/mini_toolbox/contextlib.py",
    "content": "\"\"\"contextlib2 - backports and enhancements to the contextlib module\"\"\"\n\nimport sys\nimport warnings\nfrom collections import deque\nfrom functools import wraps\n\n__all__ = [\"contextmanager\", \"closing\", \"ContextDecorator\", \"ExitStack\",\n           \"redirect_stdout\", \"redirect_stderr\", \"suppress\"]\n\n# Backwards compatibility\n__all__ += [\"ContextStack\"]\n\nclass ContextDecorator(object):\n    \"A base class or mixin that enables context managers to work as decorators.\"\n\n    def refresh_cm(self):\n        \"\"\"Returns the context manager used to actually wrap the call to the\n        decorated function.\n\n        The default implementation just returns *self*.\n\n        Overriding this method allows otherwise one-shot context managers\n        like _GeneratorContextManager to support use as decorators via\n        implicit recreation.\n\n        DEPRECATED: refresh_cm was never added to the standard library's\n                    ContextDecorator API\n        \"\"\"\n        warnings.warn(\"refresh_cm was never added to the standard library\",\n                      DeprecationWarning)\n        return self._recreate_cm()\n\n    def _recreate_cm(self):\n        \"\"\"Return a recreated instance of self.\n\n        Allows an otherwise one-shot context manager like\n        _GeneratorContextManager to support use as\n        a decorator via implicit recreation.\n\n        This is a private interface just for _GeneratorContextManager.\n        See issue #11647 for details.\n        \"\"\"\n        return self\n\n    def __call__(self, func):\n        @wraps(func)\n        def inner(*args, **kwds):\n            with self._recreate_cm():\n                return func(*args, **kwds)\n        return inner\n\n\nclass _GeneratorContextManager(ContextDecorator):\n    \"\"\"Helper for @contextmanager decorator.\"\"\"\n\n    def __init__(self, func, args, kwds):\n        self.gen = func(*args, **kwds)\n        self.func, self.args, self.kwds = func, args, kwds\n        # Issue 19330: ensure context manager instances have good docstrings\n        doc = getattr(func, \"__doc__\", None)\n        if doc is None:\n            doc = type(self).__doc__\n        self.__doc__ = doc\n        # Unfortunately, this still doesn't provide good help output when\n        # inspecting the created context manager instances, since pydoc\n        # currently bypasses the instance docstring and shows the docstring\n        # for the class instead.\n        # See http://bugs.python.org/issue19404 for more details.\n\n    def _recreate_cm(self):\n        # _GCM instances are one-shot context managers, so the\n        # CM must be recreated each time a decorated function is\n        # called\n        return self.__class__(self.func, self.args, self.kwds)\n\n    def __enter__(self):\n        try:\n            return next(self.gen)\n        except StopIteration:\n            raise RuntimeError(\"generator didn't yield\")\n\n    def __exit__(self, type, value, traceback):\n        if type is None:\n            try:\n                next(self.gen)\n            except StopIteration:\n                return\n            else:\n                raise RuntimeError(\"generator didn't stop\")\n        else:\n            if value is None:\n                # Need to force instantiation so we can reliably\n                # tell if we get the same exception back\n                value = type()\n            try:\n                self.gen.throw(type, value, traceback)\n                raise RuntimeError(\"generator didn't stop after throw()\")\n            except StopIteration as exc:\n                # Suppress StopIteration *unless* it's the same exception that\n                # was passed to throw().  This prevents a StopIteration\n                # raised inside the \"with\" statement from being suppressed.\n                return exc is not value\n            except RuntimeError as exc:\n                # Don't re-raise the passed in exception\n                if exc is value:\n                    return False\n                # Likewise, avoid suppressing if a StopIteration exception\n                # was passed to throw() and later wrapped into a RuntimeError\n                # (see PEP 479).\n                if _HAVE_EXCEPTION_CHAINING and exc.__cause__ is value:\n                    return False\n                raise\n            except:\n                # only re-raise if it's *not* the exception that was\n                # passed to throw(), because __exit__() must not raise\n                # an exception unless __exit__() itself failed.  But throw()\n                # has to raise the exception to signal propagation, so this\n                # fixes the impedance mismatch between the throw() protocol\n                # and the __exit__() protocol.\n                #\n                if sys.exc_info()[1] is not value:\n                    raise\n\n\ndef contextmanager(func):\n    \"\"\"@contextmanager decorator.\n\n    Typical usage:\n\n        @contextmanager\n        def some_generator(<arguments>):\n            <setup>\n            try:\n                yield <value>\n            finally:\n                <cleanup>\n\n    This makes this:\n\n        with some_generator(<arguments>) as <variable>:\n            <body>\n\n    equivalent to this:\n\n        <setup>\n        try:\n            <variable> = <value>\n            <body>\n        finally:\n            <cleanup>\n\n    \"\"\"\n    @wraps(func)\n    def helper(*args, **kwds):\n        return _GeneratorContextManager(func, args, kwds)\n    return helper\n\n\nclass closing(object):\n    \"\"\"Context to automatically close something at the end of a block.\n\n    Code like this:\n\n        with closing(<module>.open(<arguments>)) as f:\n            <block>\n\n    is equivalent to this:\n\n        f = <module>.open(<arguments>)\n        try:\n            <block>\n        finally:\n            f.close()\n\n    \"\"\"\n    def __init__(self, thing):\n        self.thing = thing\n    def __enter__(self):\n        return self.thing\n    def __exit__(self, *exc_info):\n        self.thing.close()\n\n\nclass _RedirectStream(object):\n\n    _stream = None\n\n    def __init__(self, new_target):\n        self._new_target = new_target\n        # We use a list of old targets to make this CM re-entrant\n        self._old_targets = []\n\n    def __enter__(self):\n        self._old_targets.append(getattr(sys, self._stream))\n        setattr(sys, self._stream, self._new_target)\n        return self._new_target\n\n    def __exit__(self, exctype, excinst, exctb):\n        setattr(sys, self._stream, self._old_targets.pop())\n\n\nclass redirect_stdout(_RedirectStream):\n    \"\"\"Context manager for temporarily redirecting stdout to another file.\n\n        # How to send help() to stderr\n        with redirect_stdout(sys.stderr):\n            help(dir)\n\n        # How to write help() to a file\n        with open('help.txt', 'w') as f:\n            with redirect_stdout(f):\n                help(pow)\n    \"\"\"\n\n    _stream = \"stdout\"\n\n\nclass redirect_stderr(_RedirectStream):\n    \"\"\"Context manager for temporarily redirecting stderr to another file.\"\"\"\n\n    _stream = \"stderr\"\n\n\nclass suppress(object):\n    \"\"\"Context manager to suppress specified exceptions\n\n    After the exception is suppressed, execution proceeds with the next\n    statement following the with statement.\n\n         with suppress(FileNotFoundError):\n             os.remove(somefile)\n         # Execution still resumes here if the file was already removed\n    \"\"\"\n\n    def __init__(self, *exceptions):\n        self._exceptions = exceptions\n\n    def __enter__(self):\n        pass\n\n    def __exit__(self, exctype, excinst, exctb):\n        # Unlike isinstance and issubclass, CPython exception handling\n        # currently only looks at the concrete type hierarchy (ignoring\n        # the instance and subclass checking hooks). While Guido considers\n        # that a bug rather than a feature, it's a fairly hard one to fix\n        # due to various internal implementation details. suppress provides\n        # the simpler issubclass based semantics, rather than trying to\n        # exactly reproduce the limitations of the CPython interpreter.\n        #\n        # See http://bugs.python.org/issue12029 for more details\n        return exctype is not None and issubclass(exctype, self._exceptions)\n\n\n# Context manipulation is Python 3 only\n_HAVE_EXCEPTION_CHAINING = sys.version_info[0] >= 3\nif _HAVE_EXCEPTION_CHAINING:\n    def _make_context_fixer(frame_exc):\n        def _fix_exception_context(new_exc, old_exc):\n            # Context may not be correct, so find the end of the chain\n            while 1:\n                exc_context = new_exc.__context__\n                if exc_context is old_exc:\n                    # Context is already set correctly (see issue 20317)\n                    return\n                if exc_context is None or exc_context is frame_exc:\n                    break\n                new_exc = exc_context\n            # Change the end of the chain to point to the exception\n            # we expect it to reference\n            new_exc.__context__ = old_exc\n        return _fix_exception_context\n\n    def _reraise_with_existing_context(exc_details):\n        try:\n            # bare \"raise exc_details[1]\" replaces our carefully\n            # set-up context\n            fixed_ctx = exc_details[1].__context__\n            raise exc_details[1]\n        except BaseException:\n            exc_details[1].__context__ = fixed_ctx\n            raise\nelse:\n    # No exception context in Python 2\n    def _make_context_fixer(frame_exc):\n        return lambda new_exc, old_exc: None\n\n    # Use 3 argument raise in Python 2,\n    # but use exec to avoid SyntaxError in Python 3\n    def _reraise_with_existing_context(exc_details):\n        exc_type, exc_value, exc_tb = exc_details\n        exec (\"raise exc_type, exc_value, exc_tb\")\n\n# Handle old-style classes if they exist\ntry:\n    from types import InstanceType\nexcept ImportError:\n    # Python 3 doesn't have old-style classes\n    _get_type = type\nelse:\n    # Need to handle old-style context managers on Python 2\n    def _get_type(obj):\n        obj_type = type(obj)\n        if obj_type is InstanceType:\n            return obj.__class__ # Old-style class\n        return obj_type # New-style class\n\n# Inspired by discussions on http://bugs.python.org/issue13585\nclass ExitStack(object):\n    \"\"\"Context manager for dynamic management of a stack of exit callbacks\n\n    For example:\n\n        with ExitStack() as stack:\n            files = [stack.enter_context(open(fname)) for fname in filenames]\n            # All opened files will automatically be closed at the end of\n            # the with statement, even if attempts to open files later\n            # in the list raise an exception\n\n    \"\"\"\n    def __init__(self):\n        self._exit_callbacks = deque()\n\n    def pop_all(self):\n        \"\"\"Preserve the context stack by transferring it to a new instance\"\"\"\n        new_stack = type(self)()\n        new_stack._exit_callbacks = self._exit_callbacks\n        self._exit_callbacks = deque()\n        return new_stack\n\n    def _push_cm_exit(self, cm, cm_exit):\n        \"\"\"Helper to correctly register callbacks to __exit__ methods\"\"\"\n        def _exit_wrapper(*exc_details):\n            return cm_exit(cm, *exc_details)\n        _exit_wrapper.__self__ = cm\n        self.push(_exit_wrapper)\n\n    def push(self, exit):\n        \"\"\"Registers a callback with the standard __exit__ method signature\n\n        Can suppress exceptions the same way __exit__ methods can.\n\n        Also accepts any object with an __exit__ method (registering a call\n        to the method instead of the object itself)\n        \"\"\"\n        # We use an unbound method rather than a bound method to follow\n        # the standard lookup behaviour for special methods\n        _cb_type = _get_type(exit)\n        try:\n            exit_method = _cb_type.__exit__\n        except AttributeError:\n            # Not a context manager, so assume its a callable\n            self._exit_callbacks.append(exit)\n        else:\n            self._push_cm_exit(exit, exit_method)\n        return exit # Allow use as a decorator\n\n    def callback(self, callback, *args, **kwds):\n        \"\"\"Registers an arbitrary callback and arguments.\n\n        Cannot suppress exceptions.\n        \"\"\"\n        def _exit_wrapper(exc_type, exc, tb):\n            callback(*args, **kwds)\n        # We changed the signature, so using @wraps is not appropriate, but\n        # setting __wrapped__ may still help with introspection\n        _exit_wrapper.__wrapped__ = callback\n        self.push(_exit_wrapper)\n        return callback # Allow use as a decorator\n\n    def enter_context(self, cm):\n        \"\"\"Enters the supplied context manager\n\n        If successful, also pushes its __exit__ method as a callback and\n        returns the result of the __enter__ method.\n        \"\"\"\n        # We look up the special methods on the type to match the with statement\n        _cm_type = _get_type(cm)\n        _exit = _cm_type.__exit__\n        result = _cm_type.__enter__(cm)\n        self._push_cm_exit(cm, _exit)\n        return result\n\n    def close(self):\n        \"\"\"Immediately unwind the context stack\"\"\"\n        self.__exit__(None, None, None)\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, *exc_details):\n        received_exc = exc_details[0] is not None\n\n        # We manipulate the exception state so it behaves as though\n        # we were actually nesting multiple with statements\n        frame_exc = sys.exc_info()[1]\n        _fix_exception_context = _make_context_fixer(frame_exc)\n\n        # Callbacks are invoked in LIFO order to match the behaviour of\n        # nested context managers\n        suppressed_exc = False\n        pending_raise = False\n        while self._exit_callbacks:\n            cb = self._exit_callbacks.pop()\n            try:\n                if cb(*exc_details):\n                    suppressed_exc = True\n                    pending_raise = False\n                    exc_details = (None, None, None)\n            except:\n                new_exc_details = sys.exc_info()\n                # simulate the stack of exceptions by setting the context\n                _fix_exception_context(new_exc_details[1], exc_details[1])\n                pending_raise = True\n                exc_details = new_exc_details\n        if pending_raise:\n            _reraise_with_existing_context(exc_details)\n        return received_exc and suppressed_exc\n\n# Preserve backwards compatibility\nclass ContextStack(ExitStack):\n    \"\"\"Backwards compatibility alias for ExitStack\"\"\"\n\n    def __init__(self):\n        warnings.warn(\"ContextStack has been renamed to ExitStack\",\n                      DeprecationWarning)\n        super(ContextStack, self).__init__()\n\n    def register_exit(self, callback):\n        return self.push(callback)\n\n    def register(self, callback, *args, **kwds):\n        return self.callback(callback, *args, **kwds)\n\n    def preserve(self):\n        return self.pop_all()\n"
  },
  {
    "path": "tests/mini_toolbox/pathlib.py",
    "content": "# Copyright (c) 2014-2017 Matthias C. M. Troffaes\n# Copyright (c) 2012-2014 Antoine Pitrou and contributors\n# Distributed under the terms of the MIT License.\n\nimport ctypes\nimport fnmatch\nimport functools\nimport io\nimport ntpath\nimport os\nimport posixpath\nimport re\nfrom pysnooper import pycompat\nimport sys\ntry:\n    from collections.abc import Sequence\nexcept ImportError:\n    from collections import Sequence\nfrom errno import EINVAL, ENOENT, ENOTDIR, EEXIST, EPERM, EACCES\nfrom operator import attrgetter\n\nfrom stat import (\n    S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO)\ntry:\n    from urllib import quote as urlquote_from_bytes\nexcept ImportError:\n    from urllib.parse import quote_from_bytes as urlquote_from_bytes\n\n\ntry:\n    intern = intern\nexcept NameError:\n    intern = sys.intern\n\nsupports_symlinks = True\nif os.name == 'nt':\n    import nt\n    if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):\n        from nt import _getfinalpathname\n    else:\n        supports_symlinks = False\n        _getfinalpathname = None\nelse:\n    nt = None\n\ntry:\n    from os import scandir as os_scandir\nexcept ImportError:\n    from scandir import scandir as os_scandir\n\n__all__ = [\n    \"PurePath\", \"PurePosixPath\", \"PureWindowsPath\",\n    \"Path\", \"PosixPath\", \"WindowsPath\",\n    ]\n\n#\n# Internals\n#\n\n\ndef _py2_fsencode(parts):\n    # py2 => minimal unicode support\n    assert pycompat.PY2\n    return [part.encode('ascii') if isinstance(part, pycompat.text_type)\n            else part for part in parts]\n\n\ndef _try_except_fileexistserror(try_func, except_func, else_func=None):\n    if sys.version_info >= (3, 3):\n        try:\n            try_func()\n        except FileExistsError as exc:\n            except_func(exc)\n        else:\n            if else_func is not None:\n                else_func()\n    else:\n        try:\n            try_func()\n        except EnvironmentError as exc:\n            if exc.errno != EEXIST:\n                raise\n            else:\n                except_func(exc)\n        else:\n            if else_func is not None:\n                else_func()\n\n\ndef _try_except_filenotfounderror(try_func, except_func):\n    if sys.version_info >= (3, 3):\n        try:\n            try_func()\n        except FileNotFoundError as exc:\n            except_func(exc)\n    else:\n        try:\n            try_func()\n        except EnvironmentError as exc:\n            if exc.errno != ENOENT:\n                raise\n            else:\n                except_func(exc)\n\n\ndef _try_except_permissionerror_iter(try_iter, except_iter):\n    if sys.version_info >= (3, 3):\n        try:\n            for x in try_iter():\n                yield x\n        except PermissionError as exc:\n            for x in except_iter(exc):\n                yield x\n    else:\n        try:\n            for x in try_iter():\n                yield x\n        except EnvironmentError as exc:\n            if exc.errno not in (EPERM, EACCES):\n                raise\n            else:\n                for x in except_iter(exc):\n                    yield x\n\n\ndef _win32_get_unique_path_id(path):\n    # get file information, needed for samefile on older Python versions\n    # see http://timgolden.me.uk/python/win32_how_do_i/\n    # see_if_two_files_are_the_same_file.html\n    from ctypes import POINTER, Structure, WinError\n    from ctypes.wintypes import DWORD, HANDLE, BOOL\n\n    class FILETIME(Structure):\n        _fields_ = [(\"datetime_lo\", DWORD),\n                    (\"datetime_hi\", DWORD),\n                    ]\n\n    class BY_HANDLE_FILE_INFORMATION(Structure):\n        _fields_ = [(\"attributes\", DWORD),\n                    (\"created_at\", FILETIME),\n                    (\"accessed_at\", FILETIME),\n                    (\"written_at\", FILETIME),\n                    (\"volume\", DWORD),\n                    (\"file_hi\", DWORD),\n                    (\"file_lo\", DWORD),\n                    (\"n_links\", DWORD),\n                    (\"index_hi\", DWORD),\n                    (\"index_lo\", DWORD),\n                    ]\n\n    CreateFile = ctypes.windll.kernel32.CreateFileW\n    CreateFile.argtypes = [ctypes.c_wchar_p, DWORD, DWORD, ctypes.c_void_p,\n                           DWORD, DWORD, HANDLE]\n    CreateFile.restype = HANDLE\n    GetFileInformationByHandle = (\n        ctypes.windll.kernel32.GetFileInformationByHandle)\n    GetFileInformationByHandle.argtypes = [\n        HANDLE, POINTER(BY_HANDLE_FILE_INFORMATION)]\n    GetFileInformationByHandle.restype = BOOL\n    CloseHandle = ctypes.windll.kernel32.CloseHandle\n    CloseHandle.argtypes = [HANDLE]\n    CloseHandle.restype = BOOL\n    GENERIC_READ = 0x80000000\n    FILE_SHARE_READ = 0x00000001\n    FILE_FLAG_BACKUP_SEMANTICS = 0x02000000\n    OPEN_EXISTING = 3\n    if os.path.isdir(path):\n        flags = FILE_FLAG_BACKUP_SEMANTICS\n    else:\n        flags = 0\n    hfile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ,\n                       None, OPEN_EXISTING, flags, None)\n    if hfile == 0xffffffff:\n        if sys.version_info >= (3, 3):\n            raise FileNotFoundError(path)\n        else:\n            exc = OSError(\"file not found: path\")\n            exc.errno = ENOENT\n            raise exc\n    info = BY_HANDLE_FILE_INFORMATION()\n    success = GetFileInformationByHandle(hfile, info)\n    CloseHandle(hfile)\n    if success == 0:\n        raise WinError()\n    return info.volume, info.index_hi, info.index_lo\n\n\ndef _is_wildcard_pattern(pat):\n    # Whether this pattern needs actual matching using fnmatch, or can\n    # be looked up directly as a file.\n    return \"*\" in pat or \"?\" in pat or \"[\" in pat\n\n\nclass _Flavour(object):\n\n    \"\"\"A flavour implements a particular (platform-specific) set of path\n    semantics.\"\"\"\n\n    def __init__(self):\n        self.join = self.sep.join\n\n    def parse_parts(self, parts):\n        if pycompat.PY2:\n            parts = _py2_fsencode(parts)\n        parsed = []\n        sep = self.sep\n        altsep = self.altsep\n        drv = root = ''\n        it = reversed(parts)\n        for part in it:\n            if not part:\n                continue\n            if altsep:\n                part = part.replace(altsep, sep)\n            drv, root, rel = self.splitroot(part)\n            if sep in rel:\n                for x in reversed(rel.split(sep)):\n                    if x and x != '.':\n                        parsed.append(intern(x))\n            else:\n                if rel and rel != '.':\n                    parsed.append(intern(rel))\n            if drv or root:\n                if not drv:\n                    # If no drive is present, try to find one in the previous\n                    # parts. This makes the result of parsing e.g.\n                    # (\"C:\", \"/\", \"a\") reasonably intuitive.\n                    for part in it:\n                        if not part:\n                            continue\n                        if altsep:\n                            part = part.replace(altsep, sep)\n                        drv = self.splitroot(part)[0]\n                        if drv:\n                            break\n                break\n        if drv or root:\n            parsed.append(drv + root)\n        parsed.reverse()\n        return drv, root, parsed\n\n    def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):\n        \"\"\"\n        Join the two paths represented by the respective\n        (drive, root, parts) tuples.  Return a new (drive, root, parts) tuple.\n        \"\"\"\n        if root2:\n            if not drv2 and drv:\n                return drv, root2, [drv + root2] + parts2[1:]\n        elif drv2:\n            if drv2 == drv or self.casefold(drv2) == self.casefold(drv):\n                # Same drive => second path is relative to the first\n                return drv, root, parts + parts2[1:]\n        else:\n            # Second path is non-anchored (common case)\n            return drv, root, parts + parts2\n        return drv2, root2, parts2\n\n\nclass _WindowsFlavour(_Flavour):\n    # Reference for Windows paths can be found at\n    # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx\n\n    sep = '\\\\'\n    altsep = '/'\n    has_drv = True\n    pathmod = ntpath\n\n    is_supported = (os.name == 'nt')\n\n    drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')\n    ext_namespace_prefix = '\\\\\\\\?\\\\'\n\n    reserved_names = (\n        set(['CON', 'PRN', 'AUX', 'NUL']) |\n        set(['COM%d' % i for i in range(1, 10)]) |\n        set(['LPT%d' % i for i in range(1, 10)])\n        )\n\n    # Interesting findings about extended paths:\n    # - '\\\\?\\c:\\a', '//?/c:\\a' and '//?/c:/a' are all supported\n    #   but '\\\\?\\c:/a' is not\n    # - extended paths are always absolute; \"relative\" extended paths will\n    #   fail.\n\n    def splitroot(self, part, sep=sep):\n        first = part[0:1]\n        second = part[1:2]\n        if (second == sep and first == sep):\n            # XXX extended paths should also disable the collapsing of \".\"\n            # components (according to MSDN docs).\n            prefix, part = self._split_extended_path(part)\n            first = part[0:1]\n            second = part[1:2]\n        else:\n            prefix = ''\n        third = part[2:3]\n        if (second == sep and first == sep and third != sep):\n            # is a UNC path:\n            # vvvvvvvvvvvvvvvvvvvvv root\n            # \\\\machine\\mountpoint\\directory\\etc\\...\n            #            directory ^^^^^^^^^^^^^^\n            index = part.find(sep, 2)\n            if index != -1:\n                index2 = part.find(sep, index + 1)\n                # a UNC path can't have two slashes in a row\n                # (after the initial two)\n                if index2 != index + 1:\n                    if index2 == -1:\n                        index2 = len(part)\n                    if prefix:\n                        return prefix + part[1:index2], sep, part[index2 + 1:]\n                    else:\n                        return part[:index2], sep, part[index2 + 1:]\n        drv = root = ''\n        if second == ':' and first in self.drive_letters:\n            drv = part[:2]\n            part = part[2:]\n            first = third\n        if first == sep:\n            root = first\n            part = part.lstrip(sep)\n        return prefix + drv, root, part\n\n    def casefold(self, s):\n        return s.lower()\n\n    def casefold_parts(self, parts):\n        return [p.lower() for p in parts]\n\n    def resolve(self, path, strict=False):\n        s = str(path)\n        if not s:\n            return os.getcwd()\n        previous_s = None\n        if _getfinalpathname is not None:\n            if strict:\n                return self._ext_to_normal(_getfinalpathname(s))\n            else:\n                # End of the path after the first one not found\n                tail_parts = []\n                while True:\n                    try:\n                        s = self._ext_to_normal(_getfinalpathname(s))\n                    except FileNotFoundError:\n                        previous_s = s\n                        s, tail = os.path.split(s)\n                        tail_parts.append(tail)\n                        if previous_s == s:\n                            return path\n                    else:\n                        return os.path.join(s, *reversed(tail_parts))\n        # Means fallback on absolute\n        return None\n\n    def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):\n        prefix = ''\n        if s.startswith(ext_prefix):\n            prefix = s[:4]\n            s = s[4:]\n            if s.startswith('UNC\\\\'):\n                prefix += s[:3]\n                s = '\\\\' + s[3:]\n        return prefix, s\n\n    def _ext_to_normal(self, s):\n        # Turn back an extended path into a normal DOS-like path\n        return self._split_extended_path(s)[1]\n\n    def is_reserved(self, parts):\n        # NOTE: the rules for reserved names seem somewhat complicated\n        # (e.g. r\"..\\NUL\" is reserved but not r\"foo\\NUL\").\n        # We err on the side of caution and return True for paths which are\n        # not considered reserved by Windows.\n        if not parts:\n            return False\n        if parts[0].startswith('\\\\\\\\'):\n            # UNC paths are never reserved\n            return False\n        return parts[-1].partition('.')[0].upper() in self.reserved_names\n\n    def make_uri(self, path):\n        # Under Windows, file URIs use the UTF-8 encoding.\n        drive = path.drive\n        if len(drive) == 2 and drive[1] == ':':\n            # It's a path on a local drive => 'file:///c:/a/b'\n            rest = path.as_posix()[2:].lstrip('/')\n            return 'file:///%s/%s' % (\n                drive, urlquote_from_bytes(rest.encode('utf-8')))\n        else:\n            # It's a path on a network drive => 'file://host/share/a/b'\n            return 'file:' + urlquote_from_bytes(\n                path.as_posix().encode('utf-8'))\n\n    def gethomedir(self, username):\n        if 'HOME' in os.environ:\n            userhome = os.environ['HOME']\n        elif 'USERPROFILE' in os.environ:\n            userhome = os.environ['USERPROFILE']\n        elif 'HOMEPATH' in os.environ:\n            try:\n                drv = os.environ['HOMEDRIVE']\n            except KeyError:\n                drv = ''\n            userhome = drv + os.environ['HOMEPATH']\n        else:\n            raise RuntimeError(\"Can't determine home directory\")\n\n        if username:\n            # Try to guess user home directory.  By default all users\n            # directories are located in the same place and are named by\n            # corresponding usernames.  If current user home directory points\n            # to nonstandard place, this guess is likely wrong.\n            if os.environ['USERNAME'] != username:\n                drv, root, parts = self.parse_parts((userhome,))\n                if parts[-1] != os.environ['USERNAME']:\n                    raise RuntimeError(\"Can't determine home directory \"\n                                       \"for %r\" % username)\n                parts[-1] = username\n                if drv or root:\n                    userhome = drv + root + self.join(parts[1:])\n                else:\n                    userhome = self.join(parts)\n        return userhome\n\n\nclass _PosixFlavour(_Flavour):\n    sep = '/'\n    altsep = ''\n    has_drv = False\n    pathmod = posixpath\n\n    is_supported = (os.name != 'nt')\n\n    def splitroot(self, part, sep=sep):\n        if part and part[0] == sep:\n            stripped_part = part.lstrip(sep)\n            # According to POSIX path resolution:\n            # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/\n            # xbd_chap04.html#tag_04_11\n            # \"A pathname that begins with two successive slashes may be\n            # interpreted in an implementation-defined manner, although more\n            # than two leading slashes shall be treated as a single slash\".\n            if len(part) - len(stripped_part) == 2:\n                return '', sep * 2, stripped_part\n            else:\n                return '', sep, stripped_part\n        else:\n            return '', '', part\n\n    def casefold(self, s):\n        return s\n\n    def casefold_parts(self, parts):\n        return parts\n\n    def resolve(self, path, strict=False):\n        sep = self.sep\n        accessor = path._accessor\n        seen = {}\n\n        def _resolve(path, rest):\n            if rest.startswith(sep):\n                path = ''\n\n            for name in rest.split(sep):\n                if not name or name == '.':\n                    # current dir\n                    continue\n                if name == '..':\n                    # parent dir\n                    path, _, _ = path.rpartition(sep)\n                    continue\n                newpath = path + sep + name\n                if newpath in seen:\n                    # Already seen this path\n                    path = seen[newpath]\n                    if path is not None:\n                        # use cached value\n                        continue\n                    # The symlink is not resolved, so we must have a symlink\n                    # loop.\n                    raise RuntimeError(\"Symlink loop from %r\" % newpath)\n                # Resolve the symbolic link\n                try:\n                    target = accessor.readlink(newpath)\n                except OSError as e:\n                    if e.errno != EINVAL and strict:\n                        raise\n                    # Not a symlink, or non-strict mode. We just leave the path\n                    # untouched.\n                    path = newpath\n                else:\n                    seen[newpath] = None  # not resolved symlink\n                    path = _resolve(path, target)\n                    seen[newpath] = path  # resolved symlink\n\n            return path\n        # NOTE: according to POSIX, getcwd() cannot contain path components\n        # which are symlinks.\n        base = '' if path.is_absolute() else os.getcwd()\n        return _resolve(base, str(path)) or sep\n\n    def is_reserved(self, parts):\n        return False\n\n    def make_uri(self, path):\n        # We represent the path using the local filesystem encoding,\n        # for portability to other applications.\n        bpath = bytes(path)\n        return 'file://' + urlquote_from_bytes(bpath)\n\n    def gethomedir(self, username):\n        if not username:\n            try:\n                return os.environ['HOME']\n            except KeyError:\n                import pwd\n                return pwd.getpwuid(os.getuid()).pw_dir\n        else:\n            import pwd\n            try:\n                return pwd.getpwnam(username).pw_dir\n            except KeyError:\n                raise RuntimeError(\"Can't determine home directory \"\n                                   \"for %r\" % username)\n\n\n_windows_flavour = _WindowsFlavour()\n_posix_flavour = _PosixFlavour()\n\n\nclass _Accessor:\n\n    \"\"\"An accessor implements a particular (system-specific or not) way of\n    accessing paths on the filesystem.\"\"\"\n\n\nclass _NormalAccessor(_Accessor):\n\n    def _wrap_strfunc(strfunc):\n        @functools.wraps(strfunc)\n        def wrapped(pathobj, *args):\n            return strfunc(str(pathobj), *args)\n        return staticmethod(wrapped)\n\n    def _wrap_binary_strfunc(strfunc):\n        @functools.wraps(strfunc)\n        def wrapped(pathobjA, pathobjB, *args):\n            return strfunc(str(pathobjA), str(pathobjB), *args)\n        return staticmethod(wrapped)\n\n    stat = _wrap_strfunc(os.stat)\n\n    lstat = _wrap_strfunc(os.lstat)\n\n    open = _wrap_strfunc(os.open)\n\n    listdir = _wrap_strfunc(os.listdir)\n\n    scandir = _wrap_strfunc(os_scandir)\n\n    chmod = _wrap_strfunc(os.chmod)\n\n    if hasattr(os, \"lchmod\"):\n        lchmod = _wrap_strfunc(os.lchmod)\n    else:\n        def lchmod(self, pathobj, mode):\n            raise NotImplementedError(\"lchmod() not available on this system\")\n\n    mkdir = _wrap_strfunc(os.mkdir)\n\n    unlink = _wrap_strfunc(os.unlink)\n\n    rmdir = _wrap_strfunc(os.rmdir)\n\n    rename = _wrap_binary_strfunc(os.rename)\n\n    if sys.version_info >= (3, 3):\n        replace = _wrap_binary_strfunc(os.replace)\n\n    if nt:\n        if supports_symlinks:\n            symlink = _wrap_binary_strfunc(os.symlink)\n        else:\n            def symlink(a, b, target_is_directory):\n                raise NotImplementedError(\n                    \"symlink() not available on this system\")\n    else:\n        # Under POSIX, os.symlink() takes two args\n        @staticmethod\n        def symlink(a, b, target_is_directory):\n            return os.symlink(str(a), str(b))\n\n    utime = _wrap_strfunc(os.utime)\n\n    # Helper for resolve()\n    def readlink(self, path):\n        return os.readlink(path)\n\n\n_normal_accessor = _NormalAccessor()\n\n\n#\n# Globbing helpers\n#\n\ndef _make_selector(pattern_parts):\n    pat = pattern_parts[0]\n    child_parts = pattern_parts[1:]\n    if pat == '**':\n        cls = _RecursiveWildcardSelector\n    elif '**' in pat:\n        raise ValueError(\n            \"Invalid pattern: '**' can only be an entire path component\")\n    elif _is_wildcard_pattern(pat):\n        cls = _WildcardSelector\n    else:\n        cls = _PreciseSelector\n    return cls(pat, child_parts)\n\n\nif hasattr(functools, \"lru_cache\"):\n    _make_selector = functools.lru_cache()(_make_selector)\n\n\nclass _Selector:\n\n    \"\"\"A selector matches a specific glob pattern part against the children\n    of a given path.\"\"\"\n\n    def __init__(self, child_parts):\n        self.child_parts = child_parts\n        if child_parts:\n            self.successor = _make_selector(child_parts)\n            self.dironly = True\n        else:\n            self.successor = _TerminatingSelector()\n            self.dironly = False\n\n    def select_from(self, parent_path):\n        \"\"\"Iterate over all child paths of `parent_path` matched by this\n        selector.  This can contain parent_path itself.\"\"\"\n        path_cls = type(parent_path)\n        is_dir = path_cls.is_dir\n        exists = path_cls.exists\n        scandir = parent_path._accessor.scandir\n        if not is_dir(parent_path):\n            return iter([])\n        return self._select_from(parent_path, is_dir, exists, scandir)\n\n\nclass _TerminatingSelector:\n\n    def _select_from(self, parent_path, is_dir, exists, scandir):\n        yield parent_path\n\n\nclass _PreciseSelector(_Selector):\n\n    def __init__(self, name, child_parts):\n        self.name = name\n        _Selector.__init__(self, child_parts)\n\n    def _select_from(self, parent_path, is_dir, exists, scandir):\n        def try_iter():\n            path = parent_path._make_child_relpath(self.name)\n            if (is_dir if self.dironly else exists)(path):\n                for p in self.successor._select_from(\n                        path, is_dir, exists, scandir):\n                    yield p\n\n        def except_iter(exc):\n            return\n            yield\n\n        for x in _try_except_permissionerror_iter(try_iter, except_iter):\n            yield x\n\n\nclass _WildcardSelector(_Selector):\n\n    def __init__(self, pat, child_parts):\n        self.pat = re.compile(fnmatch.translate(pat))\n        _Selector.__init__(self, child_parts)\n\n    def _select_from(self, parent_path, is_dir, exists, scandir):\n        def try_iter():\n            cf = parent_path._flavour.casefold\n            entries = list(scandir(parent_path))\n            for entry in entries:\n                if not self.dironly or entry.is_dir():\n                    name = entry.name\n                    casefolded = cf(name)\n                    if self.pat.match(casefolded):\n                        path = parent_path._make_child_relpath(name)\n                        for p in self.successor._select_from(\n                                path, is_dir, exists, scandir):\n                            yield p\n\n        def except_iter(exc):\n            return\n            yield\n\n        for x in _try_except_permissionerror_iter(try_iter, except_iter):\n            yield x\n\n\nclass _RecursiveWildcardSelector(_Selector):\n\n    def __init__(self, pat, child_parts):\n        _Selector.__init__(self, child_parts)\n\n    def _iterate_directories(self, parent_path, is_dir, scandir):\n        yield parent_path\n\n        def try_iter():\n            entries = list(scandir(parent_path))\n            for entry in entries:\n                if entry.is_dir() and not entry.is_symlink():\n                    path = parent_path._make_child_relpath(entry.name)\n                    for p in self._iterate_directories(path, is_dir, scandir):\n                        yield p\n\n        def except_iter(exc):\n            return\n            yield\n\n        for x in _try_except_permissionerror_iter(try_iter, except_iter):\n            yield x\n\n    def _select_from(self, parent_path, is_dir, exists, scandir):\n        def try_iter():\n            yielded = set()\n            try:\n                successor_select = self.successor._select_from\n                for starting_point in self._iterate_directories(\n                        parent_path, is_dir, scandir):\n                    for p in successor_select(\n                            starting_point, is_dir, exists, scandir):\n                        if p not in yielded:\n                            yield p\n                            yielded.add(p)\n            finally:\n                yielded.clear()\n\n        def except_iter(exc):\n            return\n            yield\n\n        for x in _try_except_permissionerror_iter(try_iter, except_iter):\n            yield x\n\n\n#\n# Public API\n#\n\nclass _PathParents(Sequence):\n\n    \"\"\"This object provides sequence-like access to the logical ancestors\n    of a path.  Don't try to construct it yourself.\"\"\"\n    __slots__ = ('_pathcls', '_drv', '_root', '_parts')\n\n    def __init__(self, path):\n        # We don't store the instance to avoid reference cycles\n        self._pathcls = type(path)\n        self._drv = path._drv\n        self._root = path._root\n        self._parts = path._parts\n\n    def __len__(self):\n        if self._drv or self._root:\n            return len(self._parts) - 1\n        else:\n            return len(self._parts)\n\n    def __getitem__(self, idx):\n        if idx < 0 or idx >= len(self):\n            raise IndexError(idx)\n        return self._pathcls._from_parsed_parts(self._drv, self._root,\n                                                self._parts[:-idx - 1])\n\n    def __repr__(self):\n        return \"<{0}.parents>\".format(self._pathcls.__name__)\n\n\nclass PurePath(object):\n\n    \"\"\"PurePath represents a filesystem path and offers operations which\n    don't imply any actual filesystem I/O.  Depending on your system,\n    instantiating a PurePath will return either a PurePosixPath or a\n    PureWindowsPath object.  You can also instantiate either of these classes\n    directly, regardless of your system.\n    \"\"\"\n    __slots__ = (\n        '_drv', '_root', '_parts',\n        '_str', '_hash', '_pparts', '_cached_cparts',\n    )\n\n    def __new__(cls, *args):\n        \"\"\"Construct a PurePath from one or several strings and or existing\n        PurePath objects.  The strings and path objects are combined so as\n        to yield a canonicalized path, which is incorporated into the\n        new PurePath object.\n        \"\"\"\n        if cls is PurePath:\n            cls = PureWindowsPath if os.name == 'nt' else PurePosixPath\n        return cls._from_parts(args)\n\n    def __reduce__(self):\n        # Using the parts tuple helps share interned path parts\n        # when pickling related paths.\n        return (self.__class__, tuple(self._parts))\n\n    @classmethod\n    def _parse_args(cls, args):\n        # This is useful when you don't want to create an instance, just\n        # canonicalize some constructor arguments.\n        parts = []\n        for a in args:\n            if isinstance(a, PurePath):\n                parts += a._parts\n            else:\n                if sys.version_info >= (3, 6):\n                    a = os.fspath(a)\n                else:\n                    # duck typing for older Python versions\n                    if hasattr(a, \"__fspath__\"):\n                        a = a.__fspath__()\n                if isinstance(a, str):\n                    # Force-cast str subclasses to str (issue #21127)\n                    parts.append(str(a))\n                # also handle unicode for PY2 (pycompat.text_type = unicode)\n                elif pycompat.PY2 and isinstance(a, pycompat.text_type):\n                    # cast to str using filesystem encoding\n                    parts.append(a.encode(sys.getfilesystemencoding()))\n                else:\n                    raise TypeError(\n                        \"argument should be a str object or an os.PathLike \"\n                        \"object returning str, not %r\"\n                        % type(a))\n        return cls._flavour.parse_parts(parts)\n\n    @classmethod\n    def _from_parts(cls, args, init=True):\n        # We need to call _parse_args on the instance, so as to get the\n        # right flavour.\n        self = object.__new__(cls)\n        drv, root, parts = self._parse_args(args)\n        self._drv = drv\n        self._root = root\n        self._parts = parts\n        if init:\n            self._init()\n        return self\n\n    @classmethod\n    def _from_parsed_parts(cls, drv, root, parts, init=True):\n        self = object.__new__(cls)\n        self._drv = drv\n        self._root = root\n        self._parts = parts\n        if init:\n            self._init()\n        return self\n\n    @classmethod\n    def _format_parsed_parts(cls, drv, root, parts):\n        if drv or root:\n            return drv + root + cls._flavour.join(parts[1:])\n        else:\n            return cls._flavour.join(parts)\n\n    def _init(self):\n        # Overridden in concrete Path\n        pass\n\n    def _make_child(self, args):\n        drv, root, parts = self._parse_args(args)\n        drv, root, parts = self._flavour.join_parsed_parts(\n            self._drv, self._root, self._parts, drv, root, parts)\n        return self._from_parsed_parts(drv, root, parts)\n\n    def __str__(self):\n        \"\"\"Return the string representation of the path, suitable for\n        passing to system calls.\"\"\"\n        try:\n            return self._str\n        except AttributeError:\n            self._str = self._format_parsed_parts(self._drv, self._root,\n                                                  self._parts) or '.'\n            return self._str\n\n    def __fspath__(self):\n        return str(self)\n\n    def as_posix(self):\n        \"\"\"Return the string representation of the path with forward (/)\n        slashes.\"\"\"\n        f = self._flavour\n        return str(self).replace(f.sep, '/')\n\n    def __bytes__(self):\n        \"\"\"Return the bytes representation of the path.  This is only\n        recommended to use under Unix.\"\"\"\n        if sys.version_info < (3, 2):\n            raise NotImplementedError(\"needs Python 3.2 or later\")\n        return os.fsencode(str(self))\n\n    def __repr__(self):\n        return \"{0}({1!r})\".format(self.__class__.__name__, self.as_posix())\n\n    def as_uri(self):\n        \"\"\"Return the path as a 'file' URI.\"\"\"\n        if not self.is_absolute():\n            raise ValueError(\"relative path can't be expressed as a file URI\")\n        return self._flavour.make_uri(self)\n\n    @property\n    def _cparts(self):\n        # Cached casefolded parts, for hashing and comparison\n        try:\n            return self._cached_cparts\n        except AttributeError:\n            self._cached_cparts = self._flavour.casefold_parts(self._parts)\n            return self._cached_cparts\n\n    def __eq__(self, other):\n        if not isinstance(other, PurePath):\n            return NotImplemented\n        return (\n            self._cparts == other._cparts\n            and self._flavour is other._flavour)\n\n    def __ne__(self, other):\n        return not self == other\n\n    def __hash__(self):\n        try:\n            return self._hash\n        except AttributeError:\n            self._hash = hash(tuple(self._cparts))\n            return self._hash\n\n    def __lt__(self, other):\n        if (not isinstance(other, PurePath)\n                or self._flavour is not other._flavour):\n            return NotImplemented\n        return self._cparts < other._cparts\n\n    def __le__(self, other):\n        if (not isinstance(other, PurePath)\n                or self._flavour is not other._flavour):\n            return NotImplemented\n        return self._cparts <= other._cparts\n\n    def __gt__(self, other):\n        if (not isinstance(other, PurePath)\n                or self._flavour is not other._flavour):\n            return NotImplemented\n        return self._cparts > other._cparts\n\n    def __ge__(self, other):\n        if (not isinstance(other, PurePath)\n                or self._flavour is not other._flavour):\n            return NotImplemented\n        return self._cparts >= other._cparts\n\n    drive = property(attrgetter('_drv'),\n                     doc=\"\"\"The drive prefix (letter or UNC path), if any.\"\"\")\n\n    root = property(attrgetter('_root'),\n                    doc=\"\"\"The root of the path, if any.\"\"\")\n\n    @property\n    def anchor(self):\n        \"\"\"The concatenation of the drive and root, or ''.\"\"\"\n        anchor = self._drv + self._root\n        return anchor\n\n    @property\n    def name(self):\n        \"\"\"The final path component, if any.\"\"\"\n        parts = self._parts\n        if len(parts) == (1 if (self._drv or self._root) else 0):\n            return ''\n        return parts[-1]\n\n    @property\n    def suffix(self):\n        \"\"\"The final component's last suffix, if any.\"\"\"\n        name = self.name\n        i = name.rfind('.')\n        if 0 < i < len(name) - 1:\n            return name[i:]\n        else:\n            return ''\n\n    @property\n    def suffixes(self):\n        \"\"\"A list of the final component's suffixes, if any.\"\"\"\n        name = self.name\n        if name.endswith('.'):\n            return []\n        name = name.lstrip('.')\n        return ['.' + suffix for suffix in name.split('.')[1:]]\n\n    @property\n    def stem(self):\n        \"\"\"The final path component, minus its last suffix.\"\"\"\n        name = self.name\n        i = name.rfind('.')\n        if 0 < i < len(name) - 1:\n            return name[:i]\n        else:\n            return name\n\n    def with_name(self, name):\n        \"\"\"Return a new path with the file name changed.\"\"\"\n        if not self.name:\n            raise ValueError(\"%r has an empty name\" % (self,))\n        drv, root, parts = self._flavour.parse_parts((name,))\n        if (not name or name[-1] in [self._flavour.sep, self._flavour.altsep]\n                or drv or root or len(parts) != 1):\n            raise ValueError(\"Invalid name %r\" % (name))\n        return self._from_parsed_parts(self._drv, self._root,\n                                       self._parts[:-1] + [name])\n\n    def with_suffix(self, suffix):\n        \"\"\"Return a new path with the file suffix changed (or added, if\n        none).\n        \"\"\"\n        # XXX if suffix is None, should the current suffix be removed?\n        f = self._flavour\n        if f.sep in suffix or f.altsep and f.altsep in suffix:\n            raise ValueError(\"Invalid suffix %r\" % (suffix))\n        if suffix and not suffix.startswith('.') or suffix == '.':\n            raise ValueError(\"Invalid suffix %r\" % (suffix))\n        name = self.name\n        if not name:\n            raise ValueError(\"%r has an empty name\" % (self,))\n        old_suffix = self.suffix\n        if not old_suffix:\n            name = name + suffix\n        else:\n            name = name[:-len(old_suffix)] + suffix\n        return self._from_parsed_parts(self._drv, self._root,\n                                       self._parts[:-1] + [name])\n\n    def relative_to(self, *other):\n        \"\"\"Return the relative path to another path identified by the passed\n        arguments.  If the operation is not possible (because this is not\n        a subpath of the other path), raise ValueError.\n        \"\"\"\n        # For the purpose of this method, drive and root are considered\n        # separate parts, i.e.:\n        #   Path('c:/').relative_to('c:')  gives Path('/')\n        #   Path('c:/').relative_to('/')   raise ValueError\n        if not other:\n            raise TypeError(\"need at least one argument\")\n        parts = self._parts\n        drv = self._drv\n        root = self._root\n        if root:\n            abs_parts = [drv, root] + parts[1:]\n        else:\n            abs_parts = parts\n        to_drv, to_root, to_parts = self._parse_args(other)\n        if to_root:\n            to_abs_parts = [to_drv, to_root] + to_parts[1:]\n        else:\n            to_abs_parts = to_parts\n        n = len(to_abs_parts)\n        cf = self._flavour.casefold_parts\n        if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):\n            formatted = self._format_parsed_parts(to_drv, to_root, to_parts)\n            raise ValueError(\"{0!r} does not start with {1!r}\"\n                             .format(str(self), str(formatted)))\n        return self._from_parsed_parts('', root if n == 1 else '',\n                                       abs_parts[n:])\n\n    @property\n    def parts(self):\n        \"\"\"An object providing sequence-like access to the\n        components in the filesystem path.\"\"\"\n        # We cache the tuple to avoid building a new one each time .parts\n        # is accessed.  XXX is this necessary?\n        try:\n            return self._pparts\n        except AttributeError:\n            self._pparts = tuple(self._parts)\n            return self._pparts\n\n    def joinpath(self, *args):\n        \"\"\"Combine this path with one or several arguments, and return a\n        new path representing either a subpath (if all arguments are relative\n        paths) or a totally different path (if one of the arguments is\n        anchored).\n        \"\"\"\n        return self._make_child(args)\n\n    def __truediv__(self, key):\n        return self._make_child((key,))\n\n    def __rtruediv__(self, key):\n        return self._from_parts([key] + self._parts)\n\n    if pycompat.PY2:\n        __div__ = __truediv__\n        __rdiv__ = __rtruediv__\n\n    @property\n    def parent(self):\n        \"\"\"The logical parent of the path.\"\"\"\n        drv = self._drv\n        root = self._root\n        parts = self._parts\n        if len(parts) == 1 and (drv or root):\n            return self\n        return self._from_parsed_parts(drv, root, parts[:-1])\n\n    @property\n    def parents(self):\n        \"\"\"A sequence of this path's logical parents.\"\"\"\n        return _PathParents(self)\n\n    def is_absolute(self):\n        \"\"\"True if the path is absolute (has both a root and, if applicable,\n        a drive).\"\"\"\n        if not self._root:\n            return False\n        return not self._flavour.has_drv or bool(self._drv)\n\n    def is_reserved(self):\n        \"\"\"Return True if the path contains one of the special names reserved\n        by the system, if any.\"\"\"\n        return self._flavour.is_reserved(self._parts)\n\n    def match(self, path_pattern):\n        \"\"\"\n        Return True if this path matches the given pattern.\n        \"\"\"\n        cf = self._flavour.casefold\n        path_pattern = cf(path_pattern)\n        drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))\n        if not pat_parts:\n            raise ValueError(\"empty pattern\")\n        if drv and drv != cf(self._drv):\n            return False\n        if root and root != cf(self._root):\n            return False\n        parts = self._cparts\n        if drv or root:\n            if len(pat_parts) != len(parts):\n                return False\n            pat_parts = pat_parts[1:]\n        elif len(pat_parts) > len(parts):\n            return False\n        for part, pat in zip(reversed(parts), reversed(pat_parts)):\n            if not fnmatch.fnmatchcase(part, pat):\n                return False\n        return True\n\n\n# Can't subclass os.PathLike from PurePath and keep the constructor\n# optimizations in PurePath._parse_args().\nif sys.version_info >= (3, 6):\n    os.PathLike.register(PurePath)\n\n\nclass PurePosixPath(PurePath):\n    _flavour = _posix_flavour\n    __slots__ = ()\n\n\nclass PureWindowsPath(PurePath):\n    _flavour = _windows_flavour\n    __slots__ = ()\n\n\n# Filesystem-accessing classes\n\n\nclass Path(PurePath):\n    __slots__ = (\n        '_accessor',\n        '_closed',\n    )\n\n    def __new__(cls, *args, **kwargs):\n        if cls is Path:\n            cls = WindowsPath if os.name == 'nt' else PosixPath\n        self = cls._from_parts(args, init=False)\n        if not self._flavour.is_supported:\n            raise NotImplementedError(\"cannot instantiate %r on your system\"\n                                      % (cls.__name__,))\n        self._init()\n        return self\n\n    def _init(self,\n              # Private non-constructor arguments\n              template=None,\n              ):\n        self._closed = False\n        if template is not None:\n            self._accessor = template._accessor\n        else:\n            self._accessor = _normal_accessor\n\n    def _make_child_relpath(self, part):\n        # This is an optimization used for dir walking.  `part` must be\n        # a single part relative to this path.\n        parts = self._parts + [part]\n        return self._from_parsed_parts(self._drv, self._root, parts)\n\n    def __enter__(self):\n        if self._closed:\n            self._raise_closed()\n        return self\n\n    def __exit__(self, t, v, tb):\n        self._closed = True\n\n    def _raise_closed(self):\n        raise ValueError(\"I/O operation on closed path\")\n\n    def _opener(self, name, flags, mode=0o666):\n        # A stub for the opener argument to built-in open()\n        return self._accessor.open(self, flags, mode)\n\n    def _raw_open(self, flags, mode=0o777):\n        \"\"\"\n        Open the file pointed by this path and return a file descriptor,\n        as os.open() does.\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        return self._accessor.open(self, flags, mode)\n\n    # Public API\n\n    @classmethod\n    def cwd(cls):\n        \"\"\"Return a new path pointing to the current working directory\n        (as returned by os.getcwd()).\n        \"\"\"\n        return cls(os.getcwd())\n\n    @classmethod\n    def home(cls):\n        \"\"\"Return a new path pointing to the user's home directory (as\n        returned by os.path.expanduser('~')).\n        \"\"\"\n        return cls(cls()._flavour.gethomedir(None))\n\n    def samefile(self, other_path):\n        \"\"\"Return whether other_path is the same or not as this file\n        (as returned by os.path.samefile()).\n        \"\"\"\n        if hasattr(os.path, \"samestat\"):\n            st = self.stat()\n            try:\n                other_st = other_path.stat()\n            except AttributeError:\n                other_st = os.stat(other_path)\n            return os.path.samestat(st, other_st)\n        else:\n            filename1 = pycompat.text_type(self)\n            filename2 = pycompat.text_type(other_path)\n            st1 = _win32_get_unique_path_id(filename1)\n            st2 = _win32_get_unique_path_id(filename2)\n            return st1 == st2\n\n    def iterdir(self):\n        \"\"\"Iterate over the files in this directory.  Does not yield any\n        result for the special paths '.' and '..'.\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        for name in self._accessor.listdir(self):\n            if name in ('.', '..'):\n                # Yielding a path object for these makes little sense\n                continue\n            yield self._make_child_relpath(name)\n            if self._closed:\n                self._raise_closed()\n\n    def glob(self, pattern):\n        \"\"\"Iterate over this subtree and yield all existing files (of any\n        kind, including directories) matching the given pattern.\n        \"\"\"\n        if not pattern:\n            raise ValueError(\"Unacceptable pattern: {0!r}\".format(pattern))\n        pattern = self._flavour.casefold(pattern)\n        drv, root, pattern_parts = self._flavour.parse_parts((pattern,))\n        if drv or root:\n            raise NotImplementedError(\"Non-relative patterns are unsupported\")\n        selector = _make_selector(tuple(pattern_parts))\n        for p in selector.select_from(self):\n            yield p\n\n    def rglob(self, pattern):\n        \"\"\"Recursively yield all existing files (of any kind, including\n        directories) matching the given pattern, anywhere in this subtree.\n        \"\"\"\n        pattern = self._flavour.casefold(pattern)\n        drv, root, pattern_parts = self._flavour.parse_parts((pattern,))\n        if drv or root:\n            raise NotImplementedError(\"Non-relative patterns are unsupported\")\n        selector = _make_selector((\"**\",) + tuple(pattern_parts))\n        for p in selector.select_from(self):\n            yield p\n\n    def absolute(self):\n        \"\"\"Return an absolute version of this path.  This function works\n        even if the path doesn't point to anything.\n\n        No normalization is done, i.e. all '.' and '..' will be kept along.\n        Use resolve() to get the canonical path to a file.\n        \"\"\"\n        # XXX untested yet!\n        if self._closed:\n            self._raise_closed()\n        if self.is_absolute():\n            return self\n        # FIXME this must defer to the specific flavour (and, under Windows,\n        # use nt._getfullpathname())\n        obj = self._from_parts([os.getcwd()] + self._parts, init=False)\n        obj._init(template=self)\n        return obj\n\n    def resolve(self, strict=False):\n        \"\"\"\n        Make the path absolute, resolving all symlinks on the way and also\n        normalizing it (for example turning slashes into backslashes under\n        Windows).\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        s = self._flavour.resolve(self, strict=strict)\n        if s is None:\n            # No symlink resolution => for consistency, raise an error if\n            # the path doesn't exist or is forbidden\n            self.stat()\n            s = str(self.absolute())\n        # Now we have no symlinks in the path, it's safe to normalize it.\n        normed = self._flavour.pathmod.normpath(s)\n        obj = self._from_parts((normed,), init=False)\n        obj._init(template=self)\n        return obj\n\n    def stat(self):\n        \"\"\"\n        Return the result of the stat() system call on this path, like\n        os.stat() does.\n        \"\"\"\n        return self._accessor.stat(self)\n\n    def owner(self):\n        \"\"\"\n        Return the login name of the file owner.\n        \"\"\"\n        import pwd\n        return pwd.getpwuid(self.stat().st_uid).pw_name\n\n    def group(self):\n        \"\"\"\n        Return the group name of the file gid.\n        \"\"\"\n        import grp\n        return grp.getgrgid(self.stat().st_gid).gr_name\n\n    def open(self, mode='r', buffering=-1, encoding=None,\n             errors=None, newline=None):\n        \"\"\"\n        Open the file pointed by this path and return a file object, as\n        the built-in open() function does.\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        if sys.version_info >= (3, 3):\n            return io.open(\n                str(self), mode, buffering, encoding, errors, newline,\n                opener=self._opener)\n        else:\n            return io.open(str(self), mode, buffering,\n                           encoding, errors, newline)\n\n    def read_bytes(self):\n        \"\"\"\n        Open the file in bytes mode, read it, and close the file.\n        \"\"\"\n        with self.open(mode='rb') as f:\n            return f.read()\n\n    def read_text(self, encoding=None, errors=None):\n        \"\"\"\n        Open the file in text mode, read it, and close the file.\n        \"\"\"\n        with self.open(mode='r', encoding=encoding, errors=errors) as f:\n            return f.read()\n\n    def write_bytes(self, data):\n        \"\"\"\n        Open the file in bytes mode, write to it, and close the file.\n        \"\"\"\n        if not isinstance(data, pycompat.binary_type):\n            raise TypeError(\n                'data must be %s, not %s' %\n                (pycompat.binary_type.__name__, data.__class__.__name__))\n        with self.open(mode='wb') as f:\n            return f.write(data)\n\n    def write_text(self, data, encoding=None, errors=None):\n        \"\"\"\n        Open the file in text mode, write to it, and close the file.\n        \"\"\"\n        if not isinstance(data, pycompat.text_type):\n            raise TypeError(\n                'data must be %s, not %s' %\n                (pycompat.text_type.__name__, data.__class__.__name__))\n        with self.open(mode='w', encoding=encoding, errors=errors) as f:\n            return f.write(data)\n\n    def touch(self, mode=0o666, exist_ok=True):\n        \"\"\"\n        Create this file with the given access mode, if it doesn't exist.\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        if exist_ok:\n            # First try to bump modification time\n            # Implementation note: GNU touch uses the UTIME_NOW option of\n            # the utimensat() / futimens() functions.\n            try:\n                self._accessor.utime(self, None)\n            except OSError:\n                # Avoid exception chaining\n                pass\n            else:\n                return\n        flags = os.O_CREAT | os.O_WRONLY\n        if not exist_ok:\n            flags |= os.O_EXCL\n        fd = self._raw_open(flags, mode)\n        os.close(fd)\n\n    def mkdir(self, mode=0o777, parents=False, exist_ok=False):\n        \"\"\"\n        Create a new directory at this given path.\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n\n        def _try_func():\n            self._accessor.mkdir(self, mode)\n\n        def _exc_func(exc):\n            if not parents or self.parent == self:\n                raise exc\n            self.parent.mkdir(parents=True, exist_ok=True)\n            self.mkdir(mode, parents=False, exist_ok=exist_ok)\n\n        try:\n            _try_except_filenotfounderror(_try_func, _exc_func)\n        except OSError:\n            if not exist_ok or not self.is_dir():\n                raise\n\n    def chmod(self, mode):\n        \"\"\"\n        Change the permissions of the path, like os.chmod().\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        self._accessor.chmod(self, mode)\n\n    def lchmod(self, mode):\n        \"\"\"\n        Like chmod(), except if the path points to a symlink, the symlink's\n        permissions are changed, rather than its target's.\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        self._accessor.lchmod(self, mode)\n\n    def unlink(self):\n        \"\"\"\n        Remove this file or link.\n        If the path is a directory, use rmdir() instead.\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        self._accessor.unlink(self)\n\n    def rmdir(self):\n        \"\"\"\n        Remove this directory.  The directory must be empty.\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        self._accessor.rmdir(self)\n\n    def lstat(self):\n        \"\"\"\n        Like stat(), except if the path points to a symlink, the symlink's\n        status information is returned, rather than its target's.\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        return self._accessor.lstat(self)\n\n    def rename(self, target):\n        \"\"\"\n        Rename this path to the given path.\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        self._accessor.rename(self, target)\n\n    def replace(self, target):\n        \"\"\"\n        Rename this path to the given path, clobbering the existing\n        destination if it exists.\n        \"\"\"\n        if sys.version_info < (3, 3):\n            raise NotImplementedError(\"replace() is only available \"\n                                      \"with Python 3.3 and later\")\n        if self._closed:\n            self._raise_closed()\n        self._accessor.replace(self, target)\n\n    def symlink_to(self, target, target_is_directory=False):\n        \"\"\"\n        Make this path a symlink pointing to the given path.\n        Note the order of arguments (self, target) is the reverse of\n        os.symlink's.\n        \"\"\"\n        if self._closed:\n            self._raise_closed()\n        self._accessor.symlink(target, self, target_is_directory)\n\n    # Convenience functions for querying the stat results\n\n    def exists(self):\n        \"\"\"\n        Whether this path exists.\n        \"\"\"\n        try:\n            self.stat()\n        except OSError as e:\n            if e.errno not in (ENOENT, ENOTDIR):\n                raise\n            return False\n        return True\n\n    def is_dir(self):\n        \"\"\"\n        Whether this path is a directory.\n        \"\"\"\n        try:\n            return S_ISDIR(self.stat().st_mode)\n        except OSError as e:\n            if e.errno not in (ENOENT, ENOTDIR):\n                raise\n            # Path doesn't exist or is a broken symlink\n            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)\n            return False\n\n    def is_file(self):\n        \"\"\"\n        Whether this path is a regular file (also True for symlinks pointing\n        to regular files).\n        \"\"\"\n        try:\n            return S_ISREG(self.stat().st_mode)\n        except OSError as e:\n            if e.errno not in (ENOENT, ENOTDIR):\n                raise\n            # Path doesn't exist or is a broken symlink\n            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)\n            return False\n\n    def is_symlink(self):\n        \"\"\"\n        Whether this path is a symbolic link.\n        \"\"\"\n        try:\n            return S_ISLNK(self.lstat().st_mode)\n        except OSError as e:\n            if e.errno not in (ENOENT, ENOTDIR):\n                raise\n            # Path doesn't exist\n            return False\n\n    def is_block_device(self):\n        \"\"\"\n        Whether this path is a block device.\n        \"\"\"\n        try:\n            return S_ISBLK(self.stat().st_mode)\n        except OSError as e:\n            if e.errno not in (ENOENT, ENOTDIR):\n                raise\n            # Path doesn't exist or is a broken symlink\n            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)\n            return False\n\n    def is_char_device(self):\n        \"\"\"\n        Whether this path is a character device.\n        \"\"\"\n        try:\n            return S_ISCHR(self.stat().st_mode)\n        except OSError as e:\n            if e.errno not in (ENOENT, ENOTDIR):\n                raise\n            # Path doesn't exist or is a broken symlink\n            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)\n            return False\n\n    def is_fifo(self):\n        \"\"\"\n        Whether this path is a FIFO.\n        \"\"\"\n        try:\n            return S_ISFIFO(self.stat().st_mode)\n        except OSError as e:\n            if e.errno not in (ENOENT, ENOTDIR):\n                raise\n            # Path doesn't exist or is a broken symlink\n            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)\n            return False\n\n    def is_socket(self):\n        \"\"\"\n        Whether this path is a socket.\n        \"\"\"\n        try:\n            return S_ISSOCK(self.stat().st_mode)\n        except OSError as e:\n            if e.errno not in (ENOENT, ENOTDIR):\n                raise\n            # Path doesn't exist or is a broken symlink\n            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)\n            return False\n\n    def expanduser(self):\n        \"\"\" Return a new path with expanded ~ and ~user constructs\n        (as returned by os.path.expanduser)\n        \"\"\"\n        if (not (self._drv or self._root)\n                and self._parts and self._parts[0][:1] == '~'):\n            homedir = self._flavour.gethomedir(self._parts[0][1:])\n            return self._from_parts([homedir] + self._parts[1:])\n\n        return self\n\n\nclass PosixPath(Path, PurePosixPath):\n    __slots__ = ()\n\n\nclass WindowsPath(Path, PureWindowsPath):\n    __slots__ = ()\n\n    def owner(self):\n        raise NotImplementedError(\"Path.owner() is unsupported on this system\")\n\n    def group(self):\n        raise NotImplementedError(\"Path.group() is unsupported on this system\")\n"
  },
  {
    "path": "tests/samples/__init__.py",
    "content": ""
  },
  {
    "path": "tests/samples/exception.py",
    "content": "import pysnooper\n\n\ndef foo():\n    raise TypeError('bad')\n\n\ndef bar():\n    try:\n        foo()\n    except Exception:\n        str(1)\n        raise\n\n\n@pysnooper.snoop(depth=3, color=False)\ndef main():\n    try:\n        bar()\n    except:\n        pass\n\n\nexpected_output = '''\nSource path:... Whatever\n12:18:08.017782 call        17 def main():\n12:18:08.018142 line        18     try:\n12:18:08.018181 line        19         bar()\n    12:18:08.018223 call         8 def bar():\n    12:18:08.018260 line         9     try:\n    12:18:08.018293 line        10         foo()\n        12:18:08.018329 call         4 def foo():\n        12:18:08.018364 line         5     raise TypeError('bad')\n        12:18:08.018396 exception    5     raise TypeError('bad')\n        TypeError: bad\n        Call ended by exception\n    12:18:08.018494 exception   10         foo()\n    TypeError: bad\n    12:26:33.942623 line        11     except Exception:\n    12:26:33.942674 line        12         str(1)\n    12:18:08.018655 line        13         raise\n    Call ended by exception\n12:18:08.018718 exception   19         bar()\nTypeError: bad\n12:18:08.018761 line        20     except:\n12:18:08.018787 line        21         pass\n12:18:08.018813 return      21         pass\nReturn value:.. None\nElapsed time: 00:00:00.000885\n'''\n"
  },
  {
    "path": "tests/samples/indentation.py",
    "content": "import pysnooper\n\n\n@pysnooper.snoop(depth=2, color=False)\ndef main():\n    f2()\n\n\ndef f2():\n    f3()\n\n\ndef f3():\n    f4()\n\n\n@pysnooper.snoop(depth=2, color=False)\ndef f4():\n    f5()\n\n\ndef f5():\n    pass\n\n\nexpected_output = '''\nSource path:... Whatever\n21:10:42.298924 call         5 def main():\n21:10:42.299158 line         6     f2()\n    21:10:42.299205 call         9 def f2():\n    21:10:42.299246 line        10     f3()\n        Source path:... Whatever\n        21:10:42.299305 call        18 def f4():\n        21:10:42.299348 line        19     f5()\n            21:10:42.299386 call        22 def f5():\n            21:10:42.299424 line        23     pass\n            21:10:42.299460 return      23     pass\n            Return value:.. None\n        21:10:42.299509 return      19     f5()\n        Return value:.. None\n        Elapsed time: 00:00:00.000134\n    21:10:42.299577 return      10     f3()\n    Return value:.. None\n21:10:42.299627 return       6     f2()\nReturn value:.. None\nElapsed time: 00:00:00.000885\n'''\n"
  },
  {
    "path": "tests/samples/recursion.py",
    "content": "import pysnooper\n\n\n@pysnooper.snoop(depth=2, color=False)\ndef factorial(x):\n    if x <= 1:\n        return 1\n    return mul(x, factorial(x - 1))\n\n\ndef mul(a, b):\n    return a * b\n\n\ndef main():\n    factorial(4)\n\nexpected_output = '''\nSource path:... Whatever\nStarting var:.. x = 4\n09:31:32.691599 call         5 def factorial(x):\n09:31:32.691722 line         6     if x <= 1:\n09:31:32.691746 line         8     return mul(x, factorial(x - 1))\n    Starting var:.. x = 3\n    09:31:32.691781 call         5 def factorial(x):\n    09:31:32.691806 line         6     if x <= 1:\n    09:31:32.691823 line         8     return mul(x, factorial(x - 1))\n        Starting var:.. x = 2\n        09:31:32.691852 call         5 def factorial(x):\n        09:31:32.691875 line         6     if x <= 1:\n        09:31:32.691892 line         8     return mul(x, factorial(x - 1))\n            Starting var:.. x = 1\n            09:31:32.691918 call         5 def factorial(x):\n            09:31:32.691941 line         6     if x <= 1:\n            09:31:32.691961 line         7         return 1\n            09:31:32.691978 return       7         return 1\n            Return value:.. 1\n            Elapsed time: 00:00:00.000092\n            Starting var:.. a = 2\n            Starting var:.. b = 1\n            09:31:32.692025 call        11 def mul(a, b):\n            09:31:32.692055 line        12     return a * b\n            09:31:32.692075 return      12     return a * b\n            Return value:.. 2\n        09:31:32.692102 return       8     return mul(x, factorial(x - 1))\n        Return value:.. 2\n        Elapsed time: 00:00:00.000283\n        Starting var:.. a = 3\n        Starting var:.. b = 2\n        09:31:32.692147 call        11 def mul(a, b):\n        09:31:32.692174 line        12     return a * b\n        09:31:32.692193 return      12     return a * b\n        Return value:.. 6\n    09:31:32.692216 return       8     return mul(x, factorial(x - 1))\n    Return value:.. 6\n    Elapsed time: 00:00:00.000468\n    Starting var:.. a = 4\n    Starting var:.. b = 6\n    09:31:32.692259 call        11 def mul(a, b):\n    09:31:32.692285 line        12     return a * b\n    09:31:32.692304 return      12     return a * b\n    Return value:.. 24\n09:31:32.692326 return       8     return mul(x, factorial(x - 1))\nReturn value:.. 24\nElapsed time: 00:00:00.000760\n'''\n"
  },
  {
    "path": "tests/test_chinese.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\nimport io\nimport textwrap\nimport threading\nimport types\nimport sys\n\nfrom pysnooper.utils import truncate\nimport pytest\n\nimport pysnooper\nfrom pysnooper import pycompat\nfrom pysnooper.variables import needs_parentheses\nfrom .utils import (assert_output, assert_sample_output, VariableEntry,\n                    CallEntry, LineEntry, ReturnEntry, OpcodeEntry,\n                    ReturnValueEntry, ExceptionEntry, ExceptionValueEntry,\n                    SourcePathEntry, CallEndedByExceptionEntry,\n                    ElapsedTimeEntry)\nfrom . import mini_toolbox\n\n\n\ndef test_chinese():\n    with mini_toolbox.create_temp_folder(prefix='pysnooper') as folder:\n        path = folder / 'foo.log'\n        @pysnooper.snoop(path, color=False)\n        def foo():\n            a = 1\n            x = '失败'\n            return 7\n\n        foo()\n        with path.open(encoding='utf-8') as file:\n            output = file.read()\n        assert_output(\n            output,\n            (\n                SourcePathEntry(),\n                CallEntry(),\n                LineEntry(),\n                VariableEntry('a'),\n                LineEntry(u\"x = '失败'\"),\n                VariableEntry(u'x', (u\"'失败'\" if pycompat.PY3 else None)),\n                LineEntry(),\n                ReturnEntry(),\n                ReturnValueEntry('7'),\n                ElapsedTimeEntry(),\n            ),\n        )\n"
  },
  {
    "path": "tests/test_mini_toolbox.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\nimport pytest\n\nfrom . import mini_toolbox\n\n\ndef test_output_capturer_doesnt_swallow_exceptions():\n    with pytest.raises(ZeroDivisionError):\n        with mini_toolbox.OutputCapturer():\n            1 / 0\n"
  },
  {
    "path": "tests/test_multiple_files/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_multiple_files/multiple_files/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_multiple_files/multiple_files/bar.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\ndef bar_function(y):\n    x = 7 * y\n    return x\n"
  },
  {
    "path": "tests/test_multiple_files/multiple_files/foo.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\nimport pysnooper\n\nfrom .bar import bar_function\n\n@pysnooper.snoop(depth=2, color=False)\ndef foo_function():\n    z = bar_function(3)\n    return z"
  },
  {
    "path": "tests/test_multiple_files/test_multiple_files.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\nimport io\nimport textwrap\nimport threading\nimport types\nimport os\nimport sys\n\nfrom pysnooper.utils import truncate\nimport pytest\n\nimport pysnooper\nfrom pysnooper.variables import needs_parentheses\nfrom ..utils import (assert_output, assert_sample_output, VariableEntry,\n                    CallEntry, LineEntry, ReturnEntry, OpcodeEntry,\n                    ReturnValueEntry, ExceptionEntry, ExceptionValueEntry,\n                    SourcePathEntry, CallEndedByExceptionEntry,\n                    ElapsedTimeEntry)\nfrom .. import mini_toolbox\nfrom .multiple_files import foo\n\n\ndef test_multiple_files():\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = foo.foo_function()\n    assert result == 21\n    output = output_capturer.string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(source_path_regex=r'.*foo\\.py$'),\n            CallEntry(),\n            LineEntry(),\n            SourcePathEntry(source_path_regex=r'.*bar\\.py$'),\n            VariableEntry(),\n            CallEntry(),\n            LineEntry(),\n            VariableEntry(),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry(),\n            SourcePathEntry(source_path_regex=r'.*foo\\.py$'),\n            VariableEntry(),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry(),\n            ElapsedTimeEntry(),\n        )\n    )\n\n\n"
  },
  {
    "path": "tests/test_not_implemented.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\nimport io\nimport textwrap\nimport threading\nimport collections\nimport types\nimport os\nimport sys\n\nfrom pysnooper.utils import truncate\nimport pytest\n\nimport pysnooper\nfrom pysnooper.variables import needs_parentheses\nfrom pysnooper import pycompat\nfrom .utils import (assert_output, assert_sample_output, VariableEntry,\n                    CallEntry, LineEntry, ReturnEntry, OpcodeEntry,\n                    ReturnValueEntry, ExceptionEntry, ExceptionValueEntry,\n                    SourcePathEntry, CallEndedByExceptionEntry,\n                    ElapsedTimeEntry)\nfrom . import mini_toolbox\n\n\ndef test_rejecting_coroutine_functions():\n    if sys.version_info[:2] <= (3, 4):\n        pytest.skip()\n\n    code = textwrap.dedent('''\n    async def foo(x):\n        return 'lol'\n    ''')\n    namespace = {}\n    exec(code, namespace)\n    foo = namespace['foo']\n\n    assert pycompat.iscoroutinefunction(foo)\n    assert not pycompat.isasyncgenfunction(foo)\n    with pytest.raises(NotImplementedError):\n        pysnooper.snoop(color=False)(foo)\n\n\ndef test_rejecting_async_generator_functions():\n    if sys.version_info[:2] <= (3, 6):\n        pytest.skip()\n\n    code = textwrap.dedent('''\n    async def foo(x):\n        yield 'lol'\n    ''')\n    namespace = {}\n    exec(code, namespace)\n    foo = namespace['foo']\n\n    assert not pycompat.iscoroutinefunction(foo)\n    assert pycompat.isasyncgenfunction(foo)\n    with pytest.raises(NotImplementedError):\n        pysnooper.snoop(color=False)(foo)\n\n\n"
  },
  {
    "path": "tests/test_pysnooper.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\nimport io\nimport textwrap\nimport threading\nimport time\nimport types\nimport os\nimport sys\nimport zipfile\n\nfrom pysnooper.utils import truncate\nimport pytest\n\nimport pysnooper\nfrom pysnooper.variables import needs_parentheses\nfrom .utils import (assert_output, assert_sample_output, VariableEntry,\n                    CallEntry, LineEntry, ReturnEntry, OpcodeEntry,\n                    ReturnValueEntry, ExceptionEntry, ExceptionValueEntry,\n                    SourcePathEntry, CallEndedByExceptionEntry,\n                    ElapsedTimeEntry)\nfrom . import mini_toolbox\n\n\ndef test_string_io():\n    string_io = io.StringIO()\n\n    @pysnooper.snoop(string_io, color=False)\n    def my_function(foo):\n        x = 7\n        y = 8\n        return y + x\n\n    result = my_function('baba')\n    assert result == 15\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('foo', value_regex=\"u?'baba'\"),\n            CallEntry('def my_function(foo):'),\n            LineEntry('x = 7'),\n            VariableEntry('x', '7'),\n            LineEntry('y = 8'),\n            VariableEntry('y', '8'),\n            LineEntry('return y + x'),\n            ReturnEntry('return y + x'),\n            ReturnValueEntry('15'),\n            ElapsedTimeEntry(),\n        )\n    )\n\n\ndef test_relative_time():\n    snoop = pysnooper.snoop(relative_time=True, color=False)\n\n    def foo(x):\n        if x == 0:\n            bar1(x)\n            qux()\n            return\n\n        with snoop:\n            # There should be line entries for these three lines,\n            # no line entries for anything else in this function,\n            # but calls to all bar functions should be traced\n            foo(x - 1)\n            bar2(x)\n            qux()\n        int(4)\n        bar3(9)\n        return x\n\n    @snoop\n    def bar1(_x):\n        qux()\n\n    @snoop\n    def bar2(_x):\n        qux()\n\n    @snoop\n    def bar3(_x):\n        qux()\n\n    def qux():\n        time.sleep(0.1)\n        return 9  # not traced, mustn't show up\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = foo(2)\n    assert result == 2\n    output = output_capturer.string_io.getvalue()\n    assert_output(\n        output,\n        (\n            # In first with\n            SourcePathEntry(),\n            VariableEntry('x', '2'),\n            VariableEntry('bar1'),\n            VariableEntry('bar2'),\n            VariableEntry('bar3'),\n            VariableEntry('foo'),\n            VariableEntry('qux'),\n            VariableEntry('snoop'),\n            LineEntry('foo(x - 1)'),\n\n            # In with in recursive call\n            VariableEntry('x', '1'),\n            VariableEntry('bar1'),\n            VariableEntry('bar2'),\n            VariableEntry('bar3'),\n            VariableEntry('foo'),\n            VariableEntry('qux'),\n            VariableEntry('snoop'),\n            LineEntry('foo(x - 1)'),\n\n            # Call to bar1 from if block outside with\n            VariableEntry('_x', '0'),\n            VariableEntry('qux'),\n            CallEntry('def bar1(_x):'),\n            LineEntry('qux()'),\n            ReturnEntry('qux()'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(0.1),\n\n            # In with in recursive call\n            LineEntry('bar2(x)'),\n\n            # Call to bar2 from within with\n            VariableEntry('_x', '1'),\n            VariableEntry('qux'),\n            CallEntry('def bar2(_x):'),\n            LineEntry('qux()'),\n            ReturnEntry('qux()'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(0.1),\n\n            # In with in recursive call\n            LineEntry('qux()'),\n            LineEntry(source_regex=\"with snoop:\", min_python_version=(3, 10)),\n            ElapsedTimeEntry(0.4),\n\n            # Call to bar3 from after with\n            VariableEntry('_x', '9'),\n            VariableEntry('qux'),\n            CallEntry('def bar3(_x):'),\n            LineEntry('qux()'),\n            ReturnEntry('qux()'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(0.1),\n\n            # -- Similar to previous few sections,\n            # -- but from first call to foo\n\n            # In with in first call\n            LineEntry('bar2(x)'),\n\n            # Call to bar2 from within with\n            VariableEntry('_x', '2'),\n            VariableEntry('qux'),\n            CallEntry('def bar2(_x):'),\n            LineEntry('qux()'),\n            ReturnEntry('qux()'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(0.1),\n\n            # In with in first call\n            LineEntry('qux()'),\n            LineEntry(source_regex=\"with snoop:\", min_python_version=(3, 10)),\n            ElapsedTimeEntry(0.7),\n\n            # Call to bar3 from after with\n            VariableEntry('_x', '9'),\n            VariableEntry('qux'),\n            CallEntry('def bar3(_x):'),\n            LineEntry('qux()'),\n            ReturnEntry('qux()'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(0.1),\n        ),\n    )\n\n\ndef test_thread_info():\n\n    @pysnooper.snoop(thread_info=True, color=False)\n    def my_function(foo):\n        x = 7\n        y = 8\n        return y + x\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = my_function('baba')\n    assert result == 15\n    output = output_capturer.string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('foo', value_regex=\"u?'baba'\"),\n            CallEntry('def my_function(foo):'),\n            LineEntry('x = 7'),\n            VariableEntry('x', '7'),\n            LineEntry('y = 8'),\n            VariableEntry('y', '8'),\n            LineEntry('return y + x'),\n            ReturnEntry('return y + x'),\n            ReturnValueEntry('15'),\n            ElapsedTimeEntry(),\n        )\n    )\n\n\ndef test_multi_thread_info():\n\n    @pysnooper.snoop(thread_info=True, color=False)\n    def my_function(foo):\n        x = 7\n        y = 8\n        return y + x\n\n    def parse_call_content(line):\n        return line.split('{event:9} '.format(event='call'))[-1]\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        my_function('baba')\n        t1 = threading.Thread(target=my_function, name=\"test123\",args=['bubu'])\n        t1.start()\n        t1.join()\n        t1 = threading.Thread(target=my_function, name=\"bibi\",args=['bibi'])\n        t1.start()\n        t1.join()\n    output = output_capturer.string_io.getvalue()\n    calls = [line for line in output.split(\"\\n\") if \"call\" in line]\n    main_thread = calls[0]\n    assert parse_call_content(main_thread) == parse_call_content(calls[1])\n    assert parse_call_content(main_thread) == parse_call_content(calls[2])\n    thread_info_regex = '([0-9]+-{name}+[ ]+)'\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('foo', value_regex=\"u?'baba'\"),\n            CallEntry('def my_function(foo):',\n                      thread_info_regex=thread_info_regex.format(\n                          name=\"MainThread\")),\n            LineEntry('x = 7',\n                      thread_info_regex=thread_info_regex.format(\n                          name=\"MainThread\")),\n            VariableEntry('x', '7'),\n            LineEntry('y = 8',\n                      thread_info_regex=thread_info_regex.format(\n                          name=\"MainThread\")),\n            VariableEntry('y', '8'),\n            LineEntry('return y + x',\n                      thread_info_regex=thread_info_regex.format(\n                          name=\"MainThread\")),\n            ReturnEntry('return y + x'),\n            ReturnValueEntry('15'),\n            ElapsedTimeEntry(),\n            VariableEntry('foo', value_regex=\"u?'bubu'\"),\n            CallEntry('def my_function(foo):',\n                      thread_info_regex=thread_info_regex.format(\n                          name=\"test123\")),\n            LineEntry('x = 7',\n                      thread_info_regex=thread_info_regex.format(\n                          name=\"test123\")),\n            VariableEntry('x', '7'),\n            LineEntry('y = 8',\n                      thread_info_regex=thread_info_regex.format(\n                          name=\"test123\")),\n            VariableEntry('y', '8'),\n            LineEntry('return y + x',\n                      thread_info_regex=thread_info_regex.format(\n                          name=\"test123\")),\n            ReturnEntry('return y + x'),\n            ReturnValueEntry('15'),\n            ElapsedTimeEntry(),\n            VariableEntry('foo', value_regex=\"u?'bibi'\"),\n            CallEntry('def my_function(foo):',\n                      thread_info_regex=thread_info_regex.format(name='bibi')),\n            LineEntry('x = 7',\n                      thread_info_regex=thread_info_regex.format(name='bibi')),\n            VariableEntry('x', '7'),\n            LineEntry('y = 8',\n                      thread_info_regex=thread_info_regex.format(name='bibi')),\n            VariableEntry('y', '8'),\n            LineEntry('return y + x',\n                      thread_info_regex=thread_info_regex.format(name='bibi')),\n            ReturnEntry('return y + x'),\n            ReturnValueEntry('15'),\n            ElapsedTimeEntry(),\n        )\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_callable(normalize):\n    string_io = io.StringIO()\n\n    def write(msg):\n        string_io.write(msg)\n\n    @pysnooper.snoop(write, normalize=normalize, color=False)\n    def my_function(foo):\n        x = 7\n        y = 8\n        return y + x\n\n    result = my_function('baba')\n    assert result == 15\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('foo', value_regex=\"u?'baba'\"),\n            CallEntry('def my_function(foo):'),\n            LineEntry('x = 7'),\n            VariableEntry('x', '7'),\n            LineEntry('y = 8'),\n            VariableEntry('y', '8'),\n            LineEntry('return y + x'),\n            ReturnEntry('return y + x'),\n            ReturnValueEntry('15'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_watch(normalize):\n    class Foo(object):\n        def __init__(self):\n            self.x = 2\n\n        def square(self):\n            self.x **= 2\n\n    @pysnooper.snoop(watch=(\n            'foo.x',\n            'io.__name__',\n            'len(foo.__dict__[\"x\"] * \"abc\")',\n    ), normalize=normalize, color=False)\n    def my_function():\n        foo = Foo()\n        for i in range(2):\n            foo.square()\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = my_function()\n    assert result is None\n    output = output_capturer.string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('Foo'),\n            VariableEntry('io.__name__', \"'io'\"),\n            CallEntry('def my_function():'),\n            LineEntry('foo = Foo()'),\n            VariableEntry('foo'),\n            VariableEntry('foo.x', '2'),\n            VariableEntry('len(foo.__dict__[\"x\"] * \"abc\")', '6'),\n            LineEntry(),\n            VariableEntry('i', '0'),\n            LineEntry(),\n            VariableEntry('foo.x', '4'),\n            VariableEntry('len(foo.__dict__[\"x\"] * \"abc\")', '12'),\n            LineEntry(),\n            VariableEntry('i', '1'),\n            LineEntry(),\n            VariableEntry('foo.x', '16'),\n            VariableEntry('len(foo.__dict__[\"x\"] * \"abc\")', '48'),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_watch_explode(normalize):\n    class Foo:\n        def __init__(self, x, y):\n            self.x = x\n            self.y = y\n\n    @pysnooper.snoop(watch_explode=('_d', '_point', 'lst + []'), normalize=normalize,\n                     color=False)\n    def my_function():\n        _d = {'a': 1, 'b': 2, 'c': 'ignore'}\n        _point = Foo(x=3, y=4)\n        lst = [7, 8, 9]\n        lst.append(10)\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = my_function()\n    assert result is None\n    output = output_capturer.string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('Foo'),\n            CallEntry('def my_function():'),\n            LineEntry(),\n            VariableEntry('_d'),\n            VariableEntry(\"_d['a']\", '1'),\n            VariableEntry(\"_d['b']\", '2'),\n            VariableEntry(\"_d['c']\", \"'ignore'\"),\n            LineEntry(),\n            VariableEntry('_point'),\n            VariableEntry('_point.x', '3'),\n            VariableEntry('_point.y', '4'),\n            LineEntry(),\n            VariableEntry('lst'),\n            VariableEntry('(lst + [])[0]', '7'),\n            VariableEntry('(lst + [])[1]', '8'),\n            VariableEntry('(lst + [])[2]', '9'),\n            VariableEntry('lst + []'),\n            LineEntry(),\n            VariableEntry('lst'),\n            VariableEntry('(lst + [])[3]', '10'),\n            VariableEntry('lst + []'),\n            ReturnEntry(),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_variables_classes(normalize):\n    class WithSlots(object):\n        __slots__ = ('x', 'y')\n\n        def __init__(self):\n            self.x = 3\n            self.y = 4\n\n    @pysnooper.snoop(watch=(\n            pysnooper.Keys('_d', exclude='c'),\n            pysnooper.Attrs('_d'),  # doesn't have attributes\n            pysnooper.Attrs('_s'),\n            pysnooper.Indices('_lst')[-3:],\n    ), normalize=normalize, color=False)\n    def my_function():\n        _d = {'a': 1, 'b': 2, 'c': 'ignore'}\n        _s = WithSlots()\n        _lst = list(range(1000))\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = my_function()\n    assert result is None\n    output = output_capturer.string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('WithSlots'),\n            CallEntry('def my_function():'),\n            LineEntry(),\n            VariableEntry('_d'),\n            VariableEntry(\"_d['a']\", '1'),\n            VariableEntry(\"_d['b']\", '2'),\n            LineEntry(),\n            VariableEntry('_s'),\n            VariableEntry('_s.x', '3'),\n            VariableEntry('_s.y', '4'),\n            LineEntry(),\n            VariableEntry('_lst'),\n            VariableEntry('_lst[997]', '997'),\n            VariableEntry('_lst[998]', '998'),\n            VariableEntry('_lst[999]', '999'),\n            ReturnEntry(),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_single_watch_no_comma(normalize):\n    class Foo(object):\n        def __init__(self):\n            self.x = 2\n\n        def square(self):\n            self.x **= 2\n\n    @pysnooper.snoop(watch='foo', normalize=normalize, color=False)\n    def my_function():\n        foo = Foo()\n        for i in range(2):\n            foo.square()\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = my_function()\n    assert result is None\n    output = output_capturer.string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('Foo'),\n            CallEntry('def my_function():'),\n            LineEntry('foo = Foo()'),\n            VariableEntry('foo'),\n            LineEntry(),\n            VariableEntry('i', '0'),\n            LineEntry(),\n            LineEntry(),\n            VariableEntry('i', '1'),\n            LineEntry(),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_long_variable(normalize):\n    @pysnooper.snoop(normalize=normalize, color=False)\n    def my_function():\n        foo = list(range(1000))\n        return foo\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = my_function()\n    assert result == list(range(1000))\n    output = output_capturer.string_io.getvalue()\n    regex = r'^(?=.{100}$)\\[0, 1, 2, .*\\.\\.\\..*, 997, 998, 999\\]$'\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            CallEntry('def my_function():'),\n            LineEntry('foo = list(range(1000))'),\n            VariableEntry('foo', value_regex=regex),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry(value_regex=regex),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_long_variable_with_custom_max_variable_length(normalize):\n    @pysnooper.snoop(max_variable_length=200, normalize=normalize, color=False)\n    def my_function():\n        foo = list(range(1000))\n        return foo\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = my_function()\n    assert result == list(range(1000))\n    output = output_capturer.string_io.getvalue()\n    regex = r'^(?=.{200}$)\\[0, 1, 2, .*\\.\\.\\..*, 997, 998, 999\\]$'\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            CallEntry('def my_function():'),\n            LineEntry('foo = list(range(1000))'),\n            VariableEntry('foo', value_regex=regex),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry(value_regex=regex),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_long_variable_with_infinite_max_variable_length(normalize):\n    @pysnooper.snoop(max_variable_length=None, normalize=normalize, color=False)\n    def my_function():\n        foo = list(range(1000))\n        return foo\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = my_function()\n    assert result == list(range(1000))\n    output = output_capturer.string_io.getvalue()\n    regex = r'^(?=.{1000,100000}$)\\[0, 1, 2, [^.]+ 997, 998, 999\\]$'\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            CallEntry('def my_function():'),\n            LineEntry('foo = list(range(1000))'),\n            VariableEntry('foo', value_regex=regex),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry(value_regex=regex),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_repr_exception(normalize):\n    class Bad(object):\n        def __repr__(self):\n            1 / 0\n\n    @pysnooper.snoop(normalize=normalize, color=False)\n    def my_function():\n        bad = Bad()\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = my_function()\n    assert result is None\n    output = output_capturer.string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('Bad'),\n            CallEntry('def my_function():'),\n            LineEntry('bad = Bad()'),\n            VariableEntry('bad', value='REPR FAILED'),\n            ReturnEntry(),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_depth(normalize):\n    string_io = io.StringIO()\n\n    def f4(x4):\n        result4 = x4 * 2\n        return result4\n\n    def f3(x3):\n        result3 = f4(x3)\n        return result3\n\n    def f2(x2):\n        result2 = f3(x2)\n        return result2\n\n    @pysnooper.snoop(string_io, depth=3, normalize=normalize, color=False)\n    def f1(x1):\n        result1 = f2(x1)\n        return result1\n\n    result = f1(10)\n    assert result == 20\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            CallEntry('def f1(x1):'),\n            LineEntry(),\n\n            VariableEntry(),\n            VariableEntry(),\n            CallEntry('def f2(x2):'),\n            LineEntry(),\n\n            VariableEntry(),\n            VariableEntry(),\n            CallEntry('def f3(x3):'),\n            LineEntry(),\n\n            VariableEntry(),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('20'),\n\n            VariableEntry(),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('20'),\n\n            VariableEntry(),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('20'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_method_and_prefix(normalize):\n    class Baz(object):\n        def __init__(self):\n            self.x = 2\n\n        @pysnooper.snoop(watch=('self.x',), prefix='ZZZ', normalize=normalize,\n                         color=False)\n        def square(self):\n            foo = 7\n            self.x **= 2\n            return self\n\n    baz = Baz()\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = baz.square()\n    assert result is baz\n    assert result.x == 4\n    output = output_capturer.string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(prefix='ZZZ'),\n            VariableEntry('self', prefix='ZZZ'),\n            VariableEntry('self.x', '2', prefix='ZZZ'),\n            CallEntry('def square(self):', prefix='ZZZ'),\n            LineEntry('foo = 7', prefix='ZZZ'),\n            VariableEntry('foo', '7', prefix='ZZZ'),\n            LineEntry('self.x **= 2', prefix='ZZZ'),\n            VariableEntry('self.x', '4', prefix='ZZZ'),\n            LineEntry(prefix='ZZZ'),\n            ReturnEntry(prefix='ZZZ'),\n            ReturnValueEntry(prefix='ZZZ'),\n            ElapsedTimeEntry(prefix='ZZZ'),\n        ),\n        prefix='ZZZ',\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_file_output(normalize):\n    with mini_toolbox.create_temp_folder(prefix='pysnooper') as folder:\n        path = folder / 'foo.log'\n\n        @pysnooper.snoop(path, normalize=normalize, color=False)\n        def my_function(_foo):\n            x = 7\n            y = 8\n            return y + x\n\n        result = my_function('baba')\n        assert result == 15\n        with path.open() as output_file:\n            output = output_file.read()\n        assert_output(\n            output,\n            (\n                SourcePathEntry(),\n                VariableEntry('_foo', value_regex=\"u?'baba'\"),\n                CallEntry('def my_function(_foo):'),\n                LineEntry('x = 7'),\n                VariableEntry('x', '7'),\n                LineEntry('y = 8'),\n                VariableEntry('y', '8'),\n                LineEntry('return y + x'),\n                ReturnEntry('return y + x'),\n                ReturnValueEntry('15'),\n                ElapsedTimeEntry(),\n            ),\n            normalize=normalize,\n        )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_confusing_decorator_lines(normalize):\n    string_io = io.StringIO()\n\n    def empty_decorator(function):\n        return function\n\n    @empty_decorator\n    @pysnooper.snoop(string_io, normalize=normalize,\n                     depth=2, color=False)\n    @empty_decorator\n    @empty_decorator\n    def my_function(foo):\n        x = lambda bar: 7\n        y = 8\n        return y + x(foo)\n\n    result = my_function('baba')\n    assert result == 15\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('foo', value_regex=\"u?'baba'\"),\n            CallEntry('def my_function(foo):'),\n            LineEntry(),\n            VariableEntry(),\n            LineEntry(),\n            VariableEntry(),\n            LineEntry(),\n            # inside lambda\n            VariableEntry('bar', value_regex=\"u?'baba'\"),\n            CallEntry('x = lambda bar: 7'),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('7'),\n            # back in my_function\n            ReturnEntry(),\n            ReturnValueEntry('15'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_lambda(normalize):\n    string_io = io.StringIO()\n    my_function = pysnooper.snoop(string_io, normalize=normalize, color=False)(lambda x: x ** 2)\n    result = my_function(7)\n    assert result == 49\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('x', '7'),\n            CallEntry(source_regex='^my_function = pysnooper.*'),\n            LineEntry(source_regex='^my_function = pysnooper.*'),\n            ReturnEntry(source_regex='^my_function = pysnooper.*'),\n            ReturnValueEntry('49'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\ndef test_unavailable_source():\n    with mini_toolbox.create_temp_folder(prefix='pysnooper') as folder, \\\n                                    mini_toolbox.TempSysPathAdder(str(folder)):\n        module_name = 'iaerojajsijf'\n        python_file_path = folder / ('%s.py' % (module_name,))\n        content = textwrap.dedent(u'''\n            import pysnooper\n            @pysnooper.snoop(color=False)\n            def f(x):\n                return x\n        ''')\n        with python_file_path.open('w') as python_file:\n            python_file.write(content)\n        module = __import__(module_name)\n        python_file_path.unlink()\n        with mini_toolbox.OutputCapturer(stdout=False,\n                                         stderr=True) as output_capturer:\n            result = getattr(module, 'f')(7)\n        assert result == 7\n        output = output_capturer.output\n        assert_output(\n            output,\n            (\n                SourcePathEntry(),\n                VariableEntry(stage='starting'),\n                CallEntry('SOURCE IS UNAVAILABLE'),\n                LineEntry('SOURCE IS UNAVAILABLE'),\n                ReturnEntry('SOURCE IS UNAVAILABLE'),\n                ReturnValueEntry('7'),\n                ElapsedTimeEntry(),\n            )\n        )\n\n\ndef test_no_overwrite_by_default():\n    with mini_toolbox.create_temp_folder(prefix='pysnooper') as folder:\n        path = folder / 'foo.log'\n        with path.open('w') as output_file:\n            output_file.write(u'lala')\n        @pysnooper.snoop(str(path), color=False)\n        def my_function(foo):\n            x = 7\n            y = 8\n            return y + x\n        result = my_function('baba')\n        assert result == 15\n        with path.open() as output_file:\n            output = output_file.read()\n        assert output.startswith('lala')\n        shortened_output = output[4:]\n        assert_output(\n            shortened_output,\n            (\n                SourcePathEntry(),\n                VariableEntry('foo', value_regex=\"u?'baba'\"),\n                CallEntry('def my_function(foo):'),\n                LineEntry('x = 7'),\n                VariableEntry('x', '7'),\n                LineEntry('y = 8'),\n                VariableEntry('y', '8'),\n                LineEntry('return y + x'),\n                ReturnEntry('return y + x'),\n                ReturnValueEntry('15'),\n                ElapsedTimeEntry(),\n            )\n        )\n\n\ndef test_overwrite():\n    with mini_toolbox.create_temp_folder(prefix='pysnooper') as folder:\n        path = folder / 'foo.log'\n        with path.open('w') as output_file:\n            output_file.write(u'lala')\n        @pysnooper.snoop(str(path), overwrite=True, color=False)\n        def my_function(foo):\n            x = 7\n            y = 8\n            return y + x\n        result = my_function('baba')\n        result = my_function('baba')\n        assert result == 15\n        with path.open() as output_file:\n            output = output_file.read()\n        assert 'lala' not in output\n        assert_output(\n            output,\n            (\n                SourcePathEntry(),\n                VariableEntry('foo', value_regex=\"u?'baba'\"),\n                CallEntry('def my_function(foo):'),\n                LineEntry('x = 7'),\n                VariableEntry('x', '7'),\n                LineEntry('y = 8'),\n                VariableEntry('y', '8'),\n                LineEntry('return y + x'),\n                ReturnEntry('return y + x'),\n                ReturnValueEntry('15'),\n                ElapsedTimeEntry(),\n\n                VariableEntry('foo', value_regex=\"u?'baba'\"),\n                CallEntry('def my_function(foo):'),\n                LineEntry('x = 7'),\n                VariableEntry('x', '7'),\n                LineEntry('y = 8'),\n                VariableEntry('y', '8'),\n                LineEntry('return y + x'),\n                ReturnEntry('return y + x'),\n                ReturnValueEntry('15'),\n                ElapsedTimeEntry(),\n            )\n        )\n\n\ndef test_error_in_overwrite_argument():\n    with mini_toolbox.create_temp_folder(prefix='pysnooper') as folder:\n        with pytest.raises(Exception, match='can only be used when writing'):\n            @pysnooper.snoop(overwrite=True, color=False)\n            def my_function(foo):\n                x = 7\n                y = 8\n                return y + x\n\n\ndef test_needs_parentheses():\n    assert not needs_parentheses('x')\n    assert not needs_parentheses('x.y')\n    assert not needs_parentheses('x.y.z')\n    assert not needs_parentheses('x.y.z[0]')\n    assert not needs_parentheses('x.y.z[0]()')\n    assert not needs_parentheses('x.y.z[0]()(3, 4 * 5)')\n    assert not needs_parentheses('foo(x)')\n    assert not needs_parentheses('foo(x+y)')\n    assert not needs_parentheses('(x+y)')\n    assert not needs_parentheses('[x+1 for x in ()]')\n    assert needs_parentheses('x + y')\n    assert needs_parentheses('x * y')\n    assert needs_parentheses('x and y')\n    assert needs_parentheses('x if z else y')\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_with_block(normalize):\n    # Testing that a single Tracer can handle many mixed uses\n    snoop = pysnooper.snoop(normalize=normalize, color=False)\n\n    def foo(x):\n        if x == 0:\n            bar1(x)\n            qux()\n            return\n\n        with snoop:\n            # There should be line entries for these three lines,\n            # no line entries for anything else in this function,\n            # but calls to all bar functions should be traced\n            foo(x - 1)\n            bar2(x)\n            qux()\n        int(4)\n        bar3(9)\n        return x\n\n    @snoop\n    def bar1(_x):\n        qux()\n\n    @snoop\n    def bar2(_x):\n        qux()\n\n    @snoop\n    def bar3(_x):\n        qux()\n\n    def qux():\n        return 9  # not traced, mustn't show up\n\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        result = foo(2)\n    assert result == 2\n    output = output_capturer.string_io.getvalue()\n    assert_output(\n        output,\n        (\n            # In first with\n            SourcePathEntry(),\n            VariableEntry('x', '2'),\n            VariableEntry('bar1'),\n            VariableEntry('bar2'),\n            VariableEntry('bar3'),\n            VariableEntry('foo'),\n            VariableEntry('qux'),\n            VariableEntry('snoop'),\n            LineEntry('foo(x - 1)'),\n\n            # In with in recursive call\n            VariableEntry('x', '1'),\n            VariableEntry('bar1'),\n            VariableEntry('bar2'),\n            VariableEntry('bar3'),\n            VariableEntry('foo'),\n            VariableEntry('qux'),\n            VariableEntry('snoop'),\n            LineEntry('foo(x - 1)'),\n\n            # Call to bar1 from if block outside with\n            VariableEntry('_x', '0'),\n            VariableEntry('qux'),\n            CallEntry('def bar1(_x):'),\n            LineEntry('qux()'),\n            ReturnEntry('qux()'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n\n            # In with in recursive call\n            LineEntry('bar2(x)'),\n\n            # Call to bar2 from within with\n            VariableEntry('_x', '1'),\n            VariableEntry('qux'),\n            CallEntry('def bar2(_x):'),\n            LineEntry('qux()'),\n            ReturnEntry('qux()'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n\n            # In with in recursive call\n            LineEntry('qux()'),\n            LineEntry(source_regex=\"with snoop:\", min_python_version=(3, 10)),\n            ElapsedTimeEntry(),\n\n            # Call to bar3 from after with\n            VariableEntry('_x', '9'),\n            VariableEntry('qux'),\n            CallEntry('def bar3(_x):'),\n            LineEntry('qux()'),\n            ReturnEntry('qux()'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n\n            # -- Similar to previous few sections,\n            # -- but from first call to foo\n\n            # In with in first call\n            LineEntry('bar2(x)'),\n\n            # Call to bar2 from within with\n            VariableEntry('_x', '2'),\n            VariableEntry('qux'),\n            CallEntry('def bar2(_x):'),\n            LineEntry('qux()'),\n            ReturnEntry('qux()'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n\n            # In with in first call\n            LineEntry('qux()'),\n            LineEntry(source_regex=\"with snoop:\", min_python_version=(3, 10)),\n            ElapsedTimeEntry(),\n\n            # Call to bar3 from after with\n            VariableEntry('_x', '9'),\n            VariableEntry('qux'),\n            CallEntry('def bar3(_x):'),\n            LineEntry('qux()'),\n            ReturnEntry('qux()'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_with_block_depth(normalize):\n    string_io = io.StringIO()\n\n    def f4(x4):\n        result4 = x4 * 2\n        return result4\n\n    def f3(x3):\n        result3 = f4(x3)\n        return result3\n\n    def f2(x2):\n        result2 = f3(x2)\n        return result2\n\n    def f1(x1):\n        str(3)\n        with pysnooper.snoop(string_io, depth=3, normalize=normalize, color=False):\n            result1 = f2(x1)\n        return result1\n\n    result = f1(10)\n    assert result == 20\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            LineEntry('result1 = f2(x1)'),\n\n            VariableEntry(),\n            VariableEntry(),\n            CallEntry('def f2(x2):'),\n            LineEntry(),\n\n            VariableEntry(),\n            VariableEntry(),\n            CallEntry('def f3(x3):'),\n            LineEntry(),\n\n            VariableEntry(),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('20'),\n\n            VariableEntry(),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('20'),\n            VariableEntry(min_python_version=(3, 10)),\n            LineEntry(source_regex=\"with pysnooper.snoop.*\", min_python_version=(3, 10)),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_cellvars(normalize):\n    string_io = io.StringIO()\n\n    def f2(a):\n        def f3(a):\n            x = 0\n            x += 1\n            def f4(a):\n                y = x\n                return 42\n            return f4(a)\n        return f3(a)\n\n    def f1(a):\n        with pysnooper.snoop(string_io, depth=4, normalize=normalize, color=False):\n            result1 = f2(a)\n        return result1\n\n    result = f1(42)\n    assert result == 42\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            LineEntry('result1 = f2(a)'),\n\n            VariableEntry(),\n            CallEntry('def f2(a):'),\n            LineEntry(),\n            VariableEntry(),\n            LineEntry(),\n\n            VariableEntry(\"a\"),\n            CallEntry('def f3(a):'),\n            LineEntry(),\n            VariableEntry(\"x\"),\n            LineEntry(),\n            VariableEntry(\"x\"),\n            LineEntry(),\n            VariableEntry(),\n\n            LineEntry(),\n            VariableEntry(),\n            VariableEntry(\"x\"),\n            CallEntry('def f4(a):'),\n            LineEntry(),\n            VariableEntry(),\n            LineEntry(),\n\n            ReturnEntry(),\n            ReturnValueEntry(),\n            ReturnEntry(),\n            ReturnValueEntry(),\n            ReturnEntry(),\n            ReturnValueEntry(),\n            VariableEntry(min_python_version=(3, 10)),\n            LineEntry(source_regex=\"with pysnooper.snoop.*\", min_python_version=(3, 10)),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_var_order(normalize):\n    string_io = io.StringIO()\n\n    def f(one, two, three, four):\n        five = None\n        six = None\n        seven = None\n\n        five, six, seven = 5, 6, 7\n\n    with pysnooper.snoop(string_io, depth=2, normalize=normalize, color=False):\n        result = f(1, 2, 3, 4)\n\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            VariableEntry(),\n\n            LineEntry('result = f(1, 2, 3, 4)'),\n            VariableEntry(\"one\", \"1\"),\n            VariableEntry(\"two\", \"2\"),\n            VariableEntry(\"three\", \"3\"),\n            VariableEntry(\"four\", \"4\"),\n\n            CallEntry('def f(one, two, three, four):'),\n            LineEntry(),\n            VariableEntry(\"five\"),\n            LineEntry(),\n            VariableEntry(\"six\"),\n            LineEntry(),\n            VariableEntry(\"seven\"),\n            LineEntry(),\n            VariableEntry(\"five\", \"5\"),\n            VariableEntry(\"six\", \"6\"),\n            VariableEntry(\"seven\", \"7\"),\n            ReturnEntry(),\n            ReturnValueEntry(),\n            VariableEntry(\"result\", \"None\", min_python_version=(3, 10)),\n            LineEntry(source_regex=\"with pysnooper.snoop.*\", min_python_version=(3, 10)),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n\ndef test_truncate():\n    max_length = 20\n    for i in range(max_length * 2):\n        string = i * 'a'\n        truncated = truncate(string, max_length)\n        if len(string) <= max_length:\n            assert string == truncated\n        else:\n            assert truncated == 'aaaaaaaa...aaaaaaaaa'\n            assert len(truncated) == max_length\n\n\ndef test_indentation():\n    from .samples import indentation, recursion\n    assert_sample_output(indentation)\n    assert_sample_output(recursion)\n\n\ndef test_exception():\n    from .samples import exception\n    assert_sample_output(exception)\n\n\ndef test_generator():\n    string_io = io.StringIO()\n    original_tracer = sys.gettrace()\n    original_tracer_active = lambda: (sys.gettrace() is original_tracer)\n\n\n    @pysnooper.snoop(string_io, color=False)\n    def f(x1):\n        assert not original_tracer_active()\n        x2 = (yield x1)\n        assert not original_tracer_active()\n        x3 = 'foo'\n        assert not original_tracer_active()\n        x4 = (yield 2)\n        assert not original_tracer_active()\n        return\n\n\n    assert original_tracer_active()\n    generator = f(0)\n    assert original_tracer_active()\n    first_item = next(generator)\n    assert original_tracer_active()\n    assert first_item == 0\n    second_item = generator.send('blabla')\n    assert original_tracer_active()\n    assert second_item == 2\n    with pytest.raises(StopIteration) as exc_info:\n        generator.send('looloo')\n    assert original_tracer_active()\n\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('x1', '0'),\n            VariableEntry(),\n            CallEntry(),\n            LineEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('0'),\n            ElapsedTimeEntry(),\n\n            # Pause and resume:\n\n            VariableEntry('x1', '0'),\n            VariableEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            CallEntry(),\n            VariableEntry('x2', \"'blabla'\"),\n            LineEntry(),\n            LineEntry(),\n            VariableEntry('x3', \"'foo'\"),\n            LineEntry(),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('2'),\n            ElapsedTimeEntry(),\n\n            # Pause and resume:\n\n            VariableEntry('x1', '0'),\n            VariableEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            VariableEntry(),\n            CallEntry(),\n            VariableEntry('x4', \"'looloo'\"),\n            LineEntry(),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry(None),\n            ElapsedTimeEntry(),\n        )\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_custom_repr(normalize):\n    string_io = io.StringIO()\n\n    def large(l):\n        return isinstance(l, list) and len(l) > 5\n\n    def print_list_size(l):\n        return 'list(size={})'.format(len(l))\n\n    def print_dict(d):\n        return 'dict(keys={})'.format(sorted(list(d.keys())))\n\n    def evil_condition(x):\n        return large(x) or isinstance(x, dict)\n\n    @pysnooper.snoop(string_io, custom_repr=(\n        (large, print_list_size),\n        (dict, print_dict),\n        (evil_condition, lambda x: 'I am evil')),\n            normalize=normalize, color=False)\n    def sum_to_x(x):\n        l = list(range(x))\n        a = {'1': 1, '2': 2}\n        return sum(l)\n\n    result = sum_to_x(10000)\n\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('x', '10000'),\n            CallEntry(),\n            LineEntry(),\n            VariableEntry('l', 'list(size=10000)'),\n            LineEntry(),\n            VariableEntry('a', \"dict(keys=['1', '2'])\"),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('49995000'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_custom_repr_single(normalize):\n    string_io = io.StringIO()\n\n    @pysnooper.snoop(string_io, custom_repr=(list, lambda l: 'foofoo!'),\n                     normalize=normalize, color=False)\n    def sum_to_x(x):\n        l = list(range(x))\n        return 7\n\n    result = sum_to_x(10000)\n\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('x', '10000'),\n            CallEntry(),\n            LineEntry(),\n            VariableEntry('l', 'foofoo!'),\n            LineEntry(),\n            ReturnEntry(),\n            ReturnValueEntry('7'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\ndef test_disable():\n    string_io = io.StringIO()\n\n    def my_function(foo):\n        x = 7\n        y = 8\n        return x + y\n\n    with mini_toolbox.TempValueSetter((pysnooper.tracer, 'DISABLED'), True):\n        tracer = pysnooper.snoop(string_io, color=False)\n        with tracer:\n            result = my_function('baba')\n        my_decorated_function = tracer(my_function)\n        my_decorated_function('booboo')\n\n    output = string_io.getvalue()\n    assert not output\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_class(normalize):\n    string_io = io.StringIO()\n\n    @pysnooper.snoop(string_io, normalize=normalize, color=False)\n    class MyClass(object):\n        def __init__(self):\n            self.x = 7\n\n        def my_method(self, foo):\n            y = 8\n            return y + self.x\n\n    instance = MyClass()\n    result = instance.my_method('baba')\n    assert result == 15\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('self', value_regex=\"u?.+MyClass object\"),\n            CallEntry('def __init__(self):'),\n            LineEntry('self.x = 7'),\n            ReturnEntry('self.x = 7'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n            VariableEntry('self', value_regex=\"u?.+MyClass object\"),\n            VariableEntry('foo', value_regex=\"u?'baba'\"),\n            CallEntry('def my_method(self, foo):'),\n            LineEntry('y = 8'),\n            VariableEntry('y', '8'),\n            LineEntry('return y + self.x'),\n            ReturnEntry('return y + self.x'),\n            ReturnValueEntry('15'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_class_with_decorated_method(normalize):\n    string_io = io.StringIO()\n\n    def decorator(function):\n        def wrapper(*args, **kwargs):\n            result = function(*args, **kwargs)\n            return result\n        return wrapper\n\n    @pysnooper.snoop(string_io, normalize=normalize, color=False)\n    class MyClass(object):\n        def __init__(self):\n            self.x = 7\n\n        @decorator\n        def my_method(self, foo):\n            y = 8\n            return y + self.x\n\n    instance = MyClass()\n    result = instance.my_method('baba')\n    assert result == 15\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('self', value_regex=\"u?.+MyClass object\"),\n            CallEntry('def __init__(self):'),\n            LineEntry('self.x = 7'),\n            ReturnEntry('self.x = 7'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n            VariableEntry('args', value_regex=r\"\\(<.+>, 'baba'\\)\"),\n            VariableEntry('kwargs', value_regex=r\"\\{\\}\"),\n            VariableEntry('function', value_regex=\"u?.+my_method\"),\n            CallEntry('def wrapper(*args, **kwargs):'),\n            LineEntry('result = function(*args, **kwargs)'),\n            VariableEntry('result', '15'),\n            LineEntry('return result'),\n            ReturnEntry('return result'),\n            ReturnValueEntry('15'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_class_with_decorated_method_and_snoop_applied_to_method(normalize):\n    string_io = io.StringIO()\n\n    def decorator(function):\n        def wrapper(*args, **kwargs):\n            result = function(*args, **kwargs)\n            return result\n        return wrapper\n\n    @pysnooper.snoop(string_io, normalize=normalize, color=False)\n    class MyClass(object):\n        def __init__(self):\n            self.x = 7\n\n        @decorator\n        @pysnooper.snoop(string_io, normalize=normalize, color=False)\n        def my_method(self, foo):\n            y = 8\n            return y + self.x\n\n    instance = MyClass()\n    result = instance.my_method('baba')\n    assert result == 15\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('self', value_regex=\"u?.*MyClass object\"),\n            CallEntry('def __init__(self):'),\n            LineEntry('self.x = 7'),\n            ReturnEntry('self.x = 7'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n            VariableEntry('args', value_regex=r\"u?\\(<.+>, 'baba'\\)\"),\n            VariableEntry('kwargs', value_regex=r\"u?\\{\\}\"),\n            VariableEntry('function', value_regex=\"u?.*my_method\"),\n            CallEntry('def wrapper(*args, **kwargs):'),\n            LineEntry('result = function(*args, **kwargs)'),\n            SourcePathEntry(),\n            VariableEntry('self', value_regex=\"u?.*MyClass object\"),\n            VariableEntry('foo', value_regex=\"u?'baba'\"),\n            CallEntry('def my_method(self, foo):'),\n            LineEntry('y = 8'),\n            VariableEntry('y', '8'),\n            LineEntry('return y + self.x'),\n            ReturnEntry('return y + self.x'),\n            ReturnValueEntry('15'),\n            ElapsedTimeEntry(),\n            VariableEntry('result', '15'),\n            LineEntry('return result'),\n            ReturnEntry('return result'),\n            ReturnValueEntry('15'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_class_with_property(normalize):\n    string_io = io.StringIO()\n\n    @pysnooper.snoop(string_io, normalize=normalize, color=False)\n    class MyClass(object):\n        def __init__(self):\n            self._x = 0\n\n        def plain_method(self):\n            pass\n\n        @property\n        def x(self):\n            self.plain_method()\n            return self._x\n\n        @x.setter\n        def x(self, value):\n            self.plain_method()\n            self._x = value\n\n        @x.deleter\n        def x(self):\n            self.plain_method()\n            del self._x\n\n    instance = MyClass()\n\n    # Do simple property operations, make sure we didn't mess up the normal behavior\n    result = instance.x\n    assert result == instance._x\n\n    instance.x = 1\n    assert instance._x == 1\n\n    del instance.x\n    with pytest.raises(AttributeError):\n        instance._x\n\n    # The property methods will not be traced, but their calls to plain_method will be.\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('self', value_regex=\"u?.*MyClass object\"),\n            CallEntry('def __init__(self):'),\n            LineEntry('self._x = 0'),\n            ReturnEntry('self._x = 0'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n\n            # Called from getter\n            VariableEntry('self', value_regex=\"u?.*MyClass object\"),\n            CallEntry('def plain_method(self):'),\n            LineEntry('pass'),\n            ReturnEntry('pass'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n\n            # Called from setter\n            VariableEntry('self', value_regex=\"u?.*MyClass object\"),\n            CallEntry('def plain_method(self):'),\n            LineEntry('pass'),\n            ReturnEntry('pass'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n\n            # Called from deleter\n            VariableEntry('self', value_regex=\"u?.*MyClass object\"),\n            CallEntry('def plain_method(self):'),\n            LineEntry('pass'),\n            ReturnEntry('pass'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\n@pytest.mark.parametrize(\"normalize\", (True, False))\ndef test_snooping_on_class_does_not_cause_base_class_to_be_snooped(normalize):\n    string_io = io.StringIO()\n\n    class UnsnoopedBaseClass(object):\n        def __init__(self):\n            self.method_on_base_class_was_called = False\n\n        def method_on_base_class(self):\n            self.method_on_base_class_was_called = True\n\n    @pysnooper.snoop(string_io, normalize=normalize, color=False)\n    class MyClass(UnsnoopedBaseClass):\n        def method_on_child_class(self):\n            self.method_on_base_class()\n\n    instance = MyClass()\n\n    assert not instance.method_on_base_class_was_called\n    instance.method_on_child_class()\n    assert instance.method_on_base_class_was_called\n\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            VariableEntry('self', value_regex=\"u?.*MyClass object\"),\n            CallEntry('def method_on_child_class(self):'),\n            LineEntry('self.method_on_base_class()'),\n            ReturnEntry('self.method_on_base_class()'),\n            ReturnValueEntry('None'),\n            ElapsedTimeEntry(),\n        ),\n        normalize=normalize,\n    )\n\n\ndef test_normalize():\n    string_io = io.StringIO()\n\n    class A:\n        def __init__(self, a):\n            self.a = a\n\n    @pysnooper.snoop(string_io, normalize=True, color=False)\n    def add():\n        a = A(19)\n        b = A(22)\n        res = a.a + b.a\n        return res\n\n    add()\n    output = string_io.getvalue()\n    assert_output(\n            output,\n            (\n                SourcePathEntry('test_pysnooper.py'),\n                VariableEntry('A', value_regex=r\"<class .*\\.A.?>\"),\n                CallEntry('def add():'),\n                LineEntry('a = A(19)'),\n                VariableEntry('a', value_regex=r\"<.*\\.A (?:object|instance)>\"),\n                LineEntry('b = A(22)'),\n                VariableEntry('b', value_regex=r\"<.*\\.A (?:object|instance)>\"),\n                LineEntry('res = a.a + b.a'),\n                VariableEntry('res', value=\"41\"),\n                LineEntry('return res'),\n                ReturnEntry('return res'),\n                ReturnValueEntry('41'),\n                ElapsedTimeEntry(),\n            )\n    )\n\n\ndef test_normalize_prefix():\n    string_io = io.StringIO()\n    _prefix = 'ZZZZ'\n\n    class A:\n        def __init__(self, a):\n            self.a = a\n\n    @pysnooper.snoop(string_io, normalize=True, prefix=_prefix, color=False)\n    def add():\n        a = A(19)\n        b = A(22)\n        res = a.a + b.a\n        return res\n\n    add()\n    output = string_io.getvalue()\n    assert_output(\n            output,\n            (\n                SourcePathEntry('test_pysnooper.py', prefix=_prefix),\n                VariableEntry('A', value_regex=r\"<class .*\\.A.?>\", prefix=_prefix),\n                CallEntry('def add():', prefix=_prefix),\n                LineEntry('a = A(19)', prefix=_prefix),\n                VariableEntry('a', value_regex=r\"<.*\\.A (?:object|instance)>\", prefix=_prefix),\n                LineEntry('b = A(22)', prefix=_prefix),\n                VariableEntry('b', value_regex=r\"<.*\\.A (?:object|instance)>\", prefix=_prefix),\n                LineEntry('res = a.a + b.a', prefix=_prefix),\n                VariableEntry('res', value=\"41\", prefix=_prefix),\n                LineEntry('return res', prefix=_prefix),\n                ReturnEntry('return res', prefix=_prefix),\n                ReturnValueEntry('41', prefix=_prefix),\n                ElapsedTimeEntry(prefix=_prefix),\n            )\n    )\n\n\ndef test_normalize_thread_info():\n    string_io = io.StringIO()\n\n    class A:\n        def __init__(self, a):\n            self.a = a\n\n    @pysnooper.snoop(string_io, normalize=True, thread_info=True, color=False)\n    def add():\n        a = A(19)\n        b = A(22)\n        res = a.a + b.a\n        return res\n\n    with pytest.raises(NotImplementedError):\n        add()\n\n\ndef test_exception():\n    string_io = io.StringIO()\n    @pysnooper.snoop(string_io, color=False)\n    def f():\n        x = 8\n        raise MemoryError\n\n    with pytest.raises(MemoryError):\n        f()\n\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            CallEntry(),\n            LineEntry(),\n            VariableEntry(),\n            LineEntry(),\n            ExceptionEntry(),\n            ExceptionValueEntry('MemoryError'),\n            CallEndedByExceptionEntry(),\n            ElapsedTimeEntry(),\n        )\n    )\n\n\n@pytest.mark.skipif(sys.version_info < (3, 11),\n                    reason='ExceptionGroup requires Python 3.11+')\ndef test_exception_group():\n    string_io = io.StringIO()\n    @pysnooper.snoop(string_io, color=False)\n    def f():\n        raise ExceptionGroup('task errors', [ValueError('bad'), TypeError('wrong'), RuntimeError('fail')])\n\n    with pytest.raises(ExceptionGroup):\n        f()\n\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            CallEntry(),\n            LineEntry(),\n            ExceptionEntry(),\n            ExceptionValueEntry(\n                value_regex=r\"ExceptionGroup: 'task errors' \"\n                            r\"\\(3 sub-exceptions: ValueError, TypeError, \"\n                            r\"RuntimeError\\)\"\n            ),\n            CallEndedByExceptionEntry(),\n            ElapsedTimeEntry(),\n        )\n    )\n\n\n@pytest.mark.skipif(sys.version_info < (3, 11),\n                    reason='ExceptionGroup requires Python 3.11+')\ndef test_nested_exception_group():\n    string_io = io.StringIO()\n    @pysnooper.snoop(string_io, color=False)\n    def f():\n        raise ExceptionGroup('outer', [ExceptionGroup('inner', [ValueError('deep')]), TypeError('shallow')])\n\n    with pytest.raises(ExceptionGroup):\n        f()\n\n    output = string_io.getvalue()\n    assert_output(\n        output,\n        (\n            SourcePathEntry(),\n            CallEntry(),\n            LineEntry(),\n            ExceptionEntry(),\n            ExceptionValueEntry(\n                value_regex=r\"ExceptionGroup: 'outer' \"\n                            r\"\\(2 sub-exceptions: ExceptionGroup, TypeError\\)\"\n            ),\n            CallEndedByExceptionEntry(),\n            ElapsedTimeEntry(),\n        )\n    )\n\n\ndef test_exception_on_entry():\n    @pysnooper.snoop(color=False)\n    def f(x):\n        pass\n\n    with pytest.raises(TypeError):\n        f()\n\n\ndef test_valid_zipfile():\n    with mini_toolbox.create_temp_folder(prefix='pysnooper') as folder, \\\n                                    mini_toolbox.TempSysPathAdder(str(folder)):\n        module_name = 'my_valid_zip_module'\n        zip_name = 'valid.zip'\n        zip_base_path = mini_toolbox.pathlib.Path('ansible/modules')\n        python_file_path = folder / zip_name / zip_base_path / ('%s.py' % (module_name))\n        os.makedirs(str(folder / zip_name / zip_base_path))\n        try:\n            sys.path.insert(0, str(folder / zip_name / zip_base_path))\n            content = textwrap.dedent(u'''\n                import pysnooper\n                @pysnooper.snoop(color=False)\n                def f(x):\n                    return x\n            ''')\n\n            python_file_path.write_text(content)\n\n            module = __import__(module_name)\n\n            with zipfile.ZipFile(str(folder / 'foo_bar.zip'), 'w') as myZipFile:\n                myZipFile.write(str(folder / zip_name / zip_base_path / ('%s.py' % (module_name))), \\\n                                '%s/%s.py' % (zip_base_path, module_name,), \\\n                                zipfile.ZIP_DEFLATED)\n\n            python_file_path.unlink()\n            folder.joinpath(zip_name).rename(folder.joinpath('%s.delete' % (zip_name)))\n            folder.joinpath('foo_bar.zip').rename(folder.joinpath(zip_name))\n\n            with mini_toolbox.OutputCapturer(stdout=False,\n                                             stderr=True) as output_capturer:\n                result = getattr(module, 'f')(7)\n            assert result == 7\n            output = output_capturer.output\n\n            assert_output(\n                output,\n                (\n                    SourcePathEntry(),\n                    VariableEntry(stage='starting'),\n                    CallEntry('def f(x):'),\n                    LineEntry('return x'),\n                    ReturnEntry('return x'),\n                    ReturnValueEntry('7'),\n                    ElapsedTimeEntry(),\n                )\n            )\n        finally:\n            sys.path.remove(str(folder / zip_name / zip_base_path))\n\n\ndef test_invalid_zipfile():\n    with mini_toolbox.create_temp_folder(prefix='pysnooper') as folder, \\\n                                    mini_toolbox.TempSysPathAdder(str(folder)):\n        module_name = 'my_invalid_zip_module'\n        zip_name = 'invalid.zip'\n        zip_base_path = mini_toolbox.pathlib.Path('invalid/modules/path')\n        python_file_path = folder / zip_name / zip_base_path / ('%s.py' % (module_name))\n        os.makedirs(str(folder / zip_name / zip_base_path))\n        try:\n            sys.path.insert(0, str(folder / zip_name / zip_base_path))\n            content = textwrap.dedent(u'''\n                import pysnooper\n                @pysnooper.snoop(color=False)\n                def f(x):\n                    return x\n            ''')\n            python_file_path.write_text(content)\n\n            module = __import__(module_name)\n\n            with zipfile.ZipFile(str(folder / 'foo_bar.zip'), 'w') as myZipFile:\n                myZipFile.write(str(folder / zip_name / zip_base_path / ('%s.py' % (module_name))), \\\n                                str(zip_base_path / ('%s.py' % (module_name,))), \\\n                                zipfile.ZIP_DEFLATED)\n\n            python_file_path.unlink()\n            folder.joinpath(zip_name).rename(folder.joinpath('%s.delete' % (zip_name)))\n            folder.joinpath('foo_bar.zip').rename(folder.joinpath(zip_name))\n\n            with mini_toolbox.OutputCapturer(stdout=False,\n                                         stderr=True) as output_capturer:\n                result = getattr(module, 'f')(7)\n            assert result == 7\n            output = output_capturer.output\n\n            assert_output(\n                output,\n                (\n                    SourcePathEntry(),\n                    VariableEntry(stage='starting'),\n                    CallEntry('SOURCE IS UNAVAILABLE'),\n                    LineEntry('SOURCE IS UNAVAILABLE'),\n                    ReturnEntry('SOURCE IS UNAVAILABLE'),\n                    ReturnValueEntry('7'),\n                    ElapsedTimeEntry(),\n                )\n            )\n        finally:\n            sys.path.remove(str(folder / zip_name / zip_base_path))\n\n\ndef test_valid_damaged_zipfile():\n    with mini_toolbox.create_temp_folder(prefix='pysnooper') as folder, \\\n                                    mini_toolbox.TempSysPathAdder(str(folder)):\n        module_name = 'my_damaged_module'\n        zip_name = 'damaged.zip'\n        zip_base_path = mini_toolbox.pathlib.Path('ansible/modules')\n        python_file_path = folder / zip_name / zip_base_path / ('%s.py' % (module_name))\n        os.makedirs(str(folder / zip_name / zip_base_path))\n        try:\n            sys.path.insert(0, str(folder / zip_name / zip_base_path))\n            content = textwrap.dedent(u'''\n                import pysnooper\n                @pysnooper.snoop(color=False)\n                def f(x):\n                    return x\n            ''')\n            python_file_path.write_text(content)\n\n            module = __import__(module_name)\n\n            python_file_path.unlink()\n            folder.joinpath(zip_name).rename(folder.joinpath('%s.delete' % (zip_name)))\n\n            folder.joinpath(zip_name).write_text(u'I am not a zip file')\n\n            with mini_toolbox.OutputCapturer(stdout=False,\n                                         stderr=True) as output_capturer:\n                result = getattr(module, 'f')(7)\n            assert result == 7\n            output = output_capturer.output\n\n            assert_output(\n                output,\n                (\n                    SourcePathEntry(),\n                    VariableEntry(stage='starting'),\n                    CallEntry('SOURCE IS UNAVAILABLE'),\n                    LineEntry('SOURCE IS UNAVAILABLE'),\n                    ReturnEntry('SOURCE IS UNAVAILABLE'),\n                    ReturnValueEntry('7'),\n                    ElapsedTimeEntry(),\n                )\n            )\n        finally:\n            sys.path.remove(str(folder / zip_name / zip_base_path))\n"
  },
  {
    "path": "tests/test_utils/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_utils/test_ensure_tuple.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\nimport pysnooper\nfrom pysnooper.utils import ensure_tuple\n\ndef test_ensure_tuple():\n    x1 = ('foo', ('foo',), ['foo'], {'foo'})\n    assert set(map(ensure_tuple, x1)) == {('foo',)}\n\n    x2 = (pysnooper.Keys('foo'), (pysnooper.Keys('foo'),),\n          [pysnooper.Keys('foo')], {pysnooper.Keys('foo')})\n\n    assert set(map(ensure_tuple, x2)) == {(pysnooper.Keys('foo'),)}\n\n\n\n\n\n"
  },
  {
    "path": "tests/test_utils/test_regex.py",
    "content": "# Copyright 2022 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\n\nimport pysnooper\nfrom pysnooper.tracer import ansible_filename_pattern\n\ndef test_ansible_filename_pattern():\n    archive_file = '/tmp/ansible_my_module_payload_xyz1234/ansible_my_module_payload.zip'\n    source_code_file = 'ansible/modules/my_module.py'\n    file_name = '%s/%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name).group(1) == archive_file\n    assert ansible_filename_pattern.match(file_name).group(2) == source_code_file\n\n    archive_file = '/tmp/ansible_my_module_payload_xyz1234/ansible_my_module_with.zip_name.zip'\n    source_code_file = 'ansible/modules/my_module.py'\n    file_name = '%s/%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name).group(1) == archive_file\n    assert ansible_filename_pattern.match(file_name).group(2) == source_code_file\n    \n    archive_file = '/my/new/path/payload.zip'\n    source_code_file = 'ansible/modules/my_module.py'\n    file_name = '%s/%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name).group(1) == archive_file\n    assert ansible_filename_pattern.match(file_name).group(2) == source_code_file\n\n    archive_file = '/tmp/ansible_my_module_payload_xyz1234/ansible_my_module_payload.zip'\n    source_code_file = 'ansible/modules/in/new/path/my_module.py'\n    file_name = '%s/%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name).group(1) == archive_file\n    assert ansible_filename_pattern.match(file_name).group(2) == source_code_file\n    \n    archive_file = '/tmp/ansible_my_module_payload_xyz1234/ansible_my_module_payload.zip'\n    source_code_file = 'ansible/modules/my_module_is_called_.py.py'\n    file_name = '%s/%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name).group(1) == archive_file\n    assert ansible_filename_pattern.match(file_name).group(2) == source_code_file\n\n    archive_file = 'C:\\\\Users\\\\vagrant\\\\AppData\\\\Local\\\\Temp\\\\pysnooperw5c2lg35\\\\valid.zip'\n    source_code_file = 'ansible\\\\modules\\\\my_valid_zip_module.py'\n    file_name = '%s\\\\%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name).group(1) == archive_file\n    assert ansible_filename_pattern.match(file_name).group(2) == source_code_file\n\n    archive_file = '/tmp/ansible_my_module_payload_xyz1234/ansible_my_module_payload.zip'\n    source_code_file = 'ANSIBLE/modules/my_module.py'\n    file_name = '%s/%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name) is None\n\n    archive_file = '/tmp/ansible_my_module_payload_xyz1234/ansible_my_module_payload.zip'\n    source_code_file = 'ansible/modules/my_module.PY'\n    file_name = '%s/%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name) is None\n\n    archive_file = '/tmp/ansible_my_module_payload_xyz1234/ansible_my_module_payload.Zip'\n    source_code_file = 'ansible/modules/my_module.py'\n    file_name = '%s/%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name) is None\n\n    archive_file = '/tmp/ansible_my_module_payload_xyz1234/ansible_my_module_payload.zip'\n    source_code_file = 'ansible/my_module.py'\n    file_name = '%s/%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name) is None\n\n    archive_file = '/tmp/ansible_my_module_payload_xyz1234/ansible_my_module_payload.zip'\n    source_code_file = ''\n    file_name = '%s/%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name) is None\n\n    archive_file = ''\n    source_code_file = 'ansible/modules/my_module.py'\n    file_name = '%s/%s' % (archive_file, source_code_file)\n    assert ansible_filename_pattern.match(file_name) is None\n"
  },
  {
    "path": "tests/utils.py",
    "content": "# Copyright 2019 Ram Rachum and collaborators.\n# This program is distributed under the MIT license.\nimport os\nimport re\nimport abc\nimport inspect\nimport sys\n\nfrom pysnooper.utils import DEFAULT_REPR_RE\n\ntry:\n    from itertools import zip_longest\nexcept ImportError:\n    from itertools import izip_longest as zip_longest\n\nfrom . import mini_toolbox\n\nimport pysnooper.pycompat\n\n\ndef get_function_arguments(function, exclude=()):\n    try:\n        getfullargspec = inspect.getfullargspec\n    except AttributeError:\n        result = inspect.getargspec(function).args\n    else:\n        result = getfullargspec(function).args\n    for exclude_item in exclude:\n        result.remove(exclude_item)\n    return result\n\n\nclass _BaseEntry(pysnooper.pycompat.ABC):\n    def __init__(self, prefix='', min_python_version=None, max_python_version=None):\n        self.prefix = prefix\n        self.min_python_version = min_python_version\n        self.max_python_version = max_python_version\n\n    @abc.abstractmethod\n    def check(self, s):\n        pass\n\n    def is_compatible_with_current_python_version(self):\n        compatible = True\n        if self.min_python_version and self.min_python_version > sys.version_info:\n            compatible = False\n        if self.max_python_version and self.max_python_version < sys.version_info:\n            compatible = False\n\n        return compatible\n\n    def __repr__(self):\n        init_arguments = get_function_arguments(self.__init__,\n                                                exclude=('self',))\n        attributes = {\n            key: repr(getattr(self, key)) for key in init_arguments\n                                              if getattr(self, key) is not None\n        }\n        return '%s(%s)' % (\n            type(self).__name__,\n            ', '.join('{key}={value}'.format(**locals()) for key, value\n                                                         in attributes.items())\n        )\n\n\n\nclass _BaseValueEntry(_BaseEntry):\n    def __init__(self, prefix='', min_python_version=None,\n                 max_python_version=None):\n        _BaseEntry.__init__(self, prefix=prefix,\n                            min_python_version=min_python_version,\n                            max_python_version=max_python_version)\n        self.line_pattern = re.compile(\n            r\"\"\"^%s(?P<indent>(?: {4})*)(?P<preamble>[^:]*):\"\"\"\n            r\"\"\"\\.{2,7} (?P<content>.*)$\"\"\" % (re.escape(self.prefix),)\n        )\n\n    @abc.abstractmethod\n    def _check_preamble(self, preamble):\n        pass\n\n    @abc.abstractmethod\n    def _check_content(self, preamble):\n        pass\n\n    def check(self, s):\n        match = self.line_pattern.match(s)\n        if not match:\n            return False\n        _, preamble, content = match.groups()\n        return (self._check_preamble(preamble) and\n                                                  self._check_content(content))\n\n\nclass ElapsedTimeEntry(_BaseEntry):\n    def __init__(self, elapsed_time_value=None, tolerance=0.2, prefix='',\n                 min_python_version=None, max_python_version=None):\n        _BaseEntry.__init__(self, prefix=prefix,\n                            min_python_version=min_python_version,\n                            max_python_version=max_python_version)\n        self.line_pattern = re.compile(\n            r\"\"\"^%s(?P<indent>(?: {4})*)Elapsed time: (?P<time>.*)\"\"\" % (\n                re.escape(self.prefix),\n            )\n        )\n        self.elapsed_time_value = elapsed_time_value\n        self.tolerance = tolerance\n\n    def check(self, s):\n        match = self.line_pattern.match(s)\n        if not match:\n            return False\n        timedelta = pysnooper.pycompat.timedelta_parse(match.group('time'))\n        if self.elapsed_time_value:\n            return abs(timedelta.total_seconds() - self.elapsed_time_value) \\\n                                                              <= self.tolerance\n        else:\n            return True\n\n\n\nclass CallEndedByExceptionEntry(_BaseEntry):\n    # Todo: Looking at this class, we could rework the hierarchy.\n    def __init__(self, prefix=''):\n        _BaseEntry.__init__(self, prefix=prefix)\n\n    def check(self, s):\n        return re.match(\n            r'''(?P<indent>(?: {4})*)Call ended by exception''',\n            s\n        )\n\n\n\nclass VariableEntry(_BaseValueEntry):\n    def __init__(self, name=None, value=None, stage=None, prefix='',\n                 name_regex=None, value_regex=None, min_python_version=None,\n                 max_python_version=None):\n        _BaseValueEntry.__init__(self, prefix=prefix,\n                                 min_python_version=min_python_version,\n                                 max_python_version=max_python_version)\n        if name is not None:\n            assert name_regex is None\n        if value is not None:\n            assert value_regex is None\n        assert stage in (None, 'starting', 'new', 'modified')\n\n        self.name = name\n        self.value = value\n        self.stage = stage\n        self.name_regex = (None if name_regex is None else\n                           re.compile(name_regex))\n        self.value_regex = (None if value_regex is None else\n                            re.compile(value_regex))\n\n    _preamble_pattern = re.compile(\n        r\"\"\"^(?P<stage>New|Modified|Starting) var$\"\"\"\n    )\n\n    def _check_preamble(self, preamble):\n        match = self._preamble_pattern.match(preamble)\n        if not match:\n            return False\n        stage = match.group('stage')\n        return self._check_stage(stage)\n\n    _content_pattern = re.compile(\n        r\"\"\"^(?P<name>.+?) = (?P<value>.+)$\"\"\"\n    )\n\n    def _check_content(self, content):\n        match = self._content_pattern.match(content)\n        if not match:\n            return False\n        name, value = match.groups()\n        return self._check_name(name) and self._check_value(value)\n\n    def _check_name(self, name):\n        if self.name is not None:\n            return name == self.name\n        elif self.name_regex is not None:\n            return self.name_regex.match(name)\n        else:\n            return True\n\n    def _check_value(self, value):\n        if self.value is not None:\n            return value == self.value\n        elif self.value_regex is not None:\n            return self.value_regex.match(value)\n        else:\n            return True\n\n    def _check_stage(self, stage):\n        stage = stage.lower()\n        if self.stage is None:\n            return stage in ('starting', 'new', 'modified')\n        else:\n            return stage == self.stage\n\n\nclass _BaseSimpleValueEntry(_BaseValueEntry):\n    def __init__(self, value=None, value_regex=None, prefix='',\n                 min_python_version=None, max_python_version=None):\n        _BaseValueEntry.__init__(self, prefix=prefix,\n                                 min_python_version=min_python_version,\n                                 max_python_version=max_python_version)\n        if value is not None:\n            assert value_regex is None\n\n        self.value = value\n        self.value_regex = (None if value_regex is None else\n                            re.compile(value_regex))\n\n    def _check_preamble(self, preamble):\n        return bool(self._preamble_pattern.match(preamble))\n\n    def _check_content(self, content):\n        return self._check_value(content)\n\n    def _check_value(self, value):\n        if self.value is not None:\n            return value == self.value\n        elif self.value_regex is not None:\n            return self.value_regex.match(value)\n        else:\n            return True\n\nclass ReturnValueEntry(_BaseSimpleValueEntry):\n    _preamble_pattern = re.compile(\n        r\"\"\"^Return value$\"\"\"\n    )\n\nclass ExceptionValueEntry(_BaseSimpleValueEntry):\n    _preamble_pattern = re.compile(\n        r\"\"\"^Exception$\"\"\"\n    )\n\nclass SourcePathEntry(_BaseValueEntry):\n    def __init__(self, source_path=None, source_path_regex=None, prefix=''):\n        _BaseValueEntry.__init__(self, prefix=prefix)\n        if source_path is not None:\n            assert source_path_regex is None\n\n        self.source_path = source_path\n        self.source_path_regex = (None if source_path_regex is None else\n                            re.compile(source_path_regex))\n\n    _preamble_pattern = re.compile(\n        r\"\"\"^Source path$\"\"\"\n    )\n\n    def _check_preamble(self, preamble):\n        return bool(self._preamble_pattern.match(preamble))\n\n    def _check_content(self, source_path):\n        if self.source_path is not None:\n            return source_path == self.source_path\n        elif self.source_path_regex is not None:\n            return self.source_path_regex.match(source_path)\n        else:\n            return True\n\n\nclass _BaseEventEntry(_BaseEntry):\n    def __init__(self, source=None, source_regex=None, thread_info=None,\n                 thread_info_regex=None, prefix='', min_python_version=None,\n                 max_python_version=None):\n        _BaseEntry.__init__(self, prefix=prefix,\n                            min_python_version=min_python_version,\n                            max_python_version=max_python_version)\n        if type(self) is _BaseEventEntry:\n            raise TypeError\n        if source is not None:\n            assert source_regex is None\n        self.line_pattern = re.compile(\n            r\"\"\"^%s(?P<indent>(?: {4})*)(?:(?:[0-9:.]{15})|(?: {15})) \"\"\"\n            r\"\"\"(?P<thread_info>[0-9]+-[0-9A-Za-z_-]+[ ]+)?\"\"\"\n            r\"\"\"(?P<event_name>[a-z_]*) +(?P<line_number>[0-9]*) \"\"\"\n            r\"\"\"+(?P<source>.*)$\"\"\" % (re.escape(self.prefix,))\n        )\n\n        self.source = source\n        self.source_regex = (None if source_regex is None else\n                             re.compile(source_regex))\n        self.thread_info = thread_info\n        self.thread_info_regex = (None if thread_info_regex is None else\n                             re.compile(thread_info_regex))\n\n    @property\n    def event_name(self):\n        return re.match('^[A-Z][a-z_]*', type(self).__name__).group(0).lower()\n\n    def _check_source(self, source):\n        if self.source is not None:\n            return source == self.source\n        elif self.source_regex is not None:\n            return self.source_regex.match(source)\n        else:\n            return True\n\n    def _check_thread_info(self, thread_info):\n        if self.thread_info is not None:\n            return thread_info == self.thread_info\n        elif self.thread_info_regex is not None:\n            return self.thread_info_regex.match(thread_info)\n        else:\n            return True\n\n    def check(self, s):\n        match = self.line_pattern.match(s)\n        if not match:\n            return False\n        _, thread_info, event_name, _, source = match.groups()\n        return (event_name == self.event_name and\n                self._check_source(source) and\n                self._check_thread_info(thread_info))\n\n\nclass CallEntry(_BaseEventEntry):\n    pass\n\n\nclass LineEntry(_BaseEventEntry):\n    pass\n\n\nclass ReturnEntry(_BaseEventEntry):\n    pass\n\n\nclass ExceptionEntry(_BaseEventEntry):\n    pass\n\n\nclass OpcodeEntry(_BaseEventEntry):\n    pass\n\n\nclass OutputFailure(Exception):\n    pass\n\n\ndef verify_normalize(lines, prefix):\n    time_re = re.compile(r\"[0-9:.]{15}\")\n    src_re = re.compile(r'^(?: *)Source path:\\.\\.\\. (.*)$')\n    for line in lines:\n        if DEFAULT_REPR_RE.search(line):\n            msg = \"normalize is active, memory address should not appear\"\n            raise OutputFailure(line, msg)\n        no_prefix = line.replace(prefix if prefix else '', '').strip()\n        if time_re.match(no_prefix):\n            msg = \"normalize is active, time should not appear\"\n            raise OutputFailure(line, msg)\n        m = src_re.match(line)\n        if m:\n            if not os.path.basename(m.group(1)) == m.group(1):\n                msg = \"normalize is active, path should be only basename\"\n                raise OutputFailure(line, msg)\n\n\ndef assert_output(output, expected_entries, prefix=None, normalize=False):\n    lines = tuple(filter(None, output.split('\\n')))\n    if expected_entries and not lines:\n        raise OutputFailure(\"Output is empty\")\n\n    if prefix is not None:\n        for line in lines:\n            if not line.startswith(prefix):\n                raise OutputFailure(line)\n\n    if normalize:\n        verify_normalize(lines, prefix)\n\n    # Filter only entries compatible with the current Python\n    filtered_expected_entries = []\n    for expected_entry in expected_entries:\n        if isinstance(expected_entry, _BaseEntry):\n            if expected_entry.is_compatible_with_current_python_version():\n                filtered_expected_entries.append(expected_entry)\n        else:\n            filtered_expected_entries.append(expected_entry)\n\n    expected_entries_count = len(filtered_expected_entries)\n    any_mismatch = False\n    result = ''\n    template = u'\\n{line!s:%s}   {expected_entry}  {arrow}' % max(map(len, lines))\n    for expected_entry, line in zip_longest(filtered_expected_entries, lines, fillvalue=\"\"):\n        mismatch = not (expected_entry and expected_entry.check(line))\n        any_mismatch |= mismatch\n        arrow = '<===' * mismatch\n        result += template.format(**locals())\n\n    if len(lines) != expected_entries_count:\n        result += '\\nOutput has {} lines, while we expect {} lines.'.format(\n                len(lines), len(expected_entries))\n\n    if any_mismatch:\n        raise OutputFailure(result)\n\n\ndef assert_sample_output(module):\n    with mini_toolbox.OutputCapturer(stdout=False,\n                                     stderr=True) as output_capturer:\n        module.main()\n\n    placeholder_time = '00:00:00.000000'\n    time_pattern = '[0-9:.]{15}'\n\n    def normalise(out):\n        out = re.sub(time_pattern, placeholder_time, out).strip()\n        out = re.sub(\n            r'^( *)Source path:\\.\\.\\. .*$',\n            r'\\1Source path:... Whatever',\n            out,\n            flags=re.MULTILINE\n        )\n        return out\n\n\n    output = output_capturer.string_io.getvalue()\n\n    try:\n        assert (\n                normalise(output) ==\n                normalise(module.expected_output)\n        )\n    except AssertionError:\n        print('\\n\\nActual Output:\\n\\n' + output)  # to copy paste into expected_output\n        raise  # show pytest diff (may need -vv flag to see in full)\n\n\n"
  },
  {
    "path": "tox.ini",
    "content": "# tox (https://tox.readthedocs.io/) is a tool for running tests\n# Run tests in multiple virtualenvs.\n\n[tox]\nenvlist =\n    flake8\n    pylint\n    bandit\n    py{27,34,35,36,37,38,39,310,py,py3}\n    readme\n    requirements\n    clean\n\n[testenv]\ndescription = Unit tests\ndeps =\n    pytest\n    py34: typing\ncommands = pytest {posargs}\n\n[testenv:bandit]\ndescription = PyCQA security linter\ndeps = bandit\ncommands = bandit -r --ini tox.ini\n\n[testenv:clean]\ndescription = Clean up bytecode\ndeps = pyclean\ncommands = py3clean -v {toxinidir}\n\n[testenv:flake8]\ndescription = Static code analysis and code style\ndeps = flake8\ncommands = flake8\n\n[testenv:pylint]\ndescription = Check for errors and code smells\ndeps = pylint\ncommands = pylint pysnooper setup\n\n[testenv:readme]\ndescription = Ensure README renders on PyPI\ndeps = twine\ncommands =\n    {envpython} setup.py -q sdist bdist_wheel\n    twine check dist/*\n\n[testenv:requirements]\ndescription = Update requirements.txt\ndeps = pip-tools\ncommands = pip-compile --output-file requirements.txt requirements.in\nchangedir = {toxinidir}\n\n[bandit]\nexclude = .tox,build,dist,tests\ntargets = .\n\n[flake8]\nexclude = .tox,build,dist,pysnooper.egg-info\n\n[pytest]\naddopts = --strict-markers\n"
  }
]