[
  {
    "path": ".flake8",
    "content": "[flake8]\nignore = E203, E266, E501, E731, W503\nmax-line-length = 80\nselect = B,C,E,F,W,T4,B9\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: tests\n\non:\n  push:\n    paths-ignore:\n      - \"*.md\"\n  pull_request:\n    types: [opened, synchronize, reopened, edited]\n    paths-ignore:\n      - \"*.md\"\n\npermissions:\n  contents: read\n\nenv:\n  MODULE_NAME: 'wasabi'\n  RUN_MYPY: 'true'\n\njobs:\n  tests:\n    name: Test\n    if: github.repository_owner == 'explosion'\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n        python_version: [\"3.12\"]\n        include:\n          - os: windows-2019\n            python_version: \"3.6\"\n          - os: windows-latest\n            python_version: \"3.7\"\n          - os: macos-latest\n            python_version: \"3.8\"\n          - os: ubuntu-latest\n            python_version: \"3.9\"\n          - os: windows-latest\n            python_version: \"3.10\"\n          - os: macos-latest\n            python_version: \"3.11\"\n    runs-on: ${{ matrix.os }}\n\n    steps:\n      - name: Check out repo\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n\n      - name: Configure Python version\n        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6\n        with:\n          python-version: ${{ matrix.python_version }}\n\n      - name: Build sdist\n        run: |\n          python -m pip install -U build pip setuptools\n          python -m pip install -U -r requirements.txt\n          python -m build --sdist\n\n      - name: Run mypy\n        shell: bash\n        if: ${{ env.RUN_MYPY == 'true' }}\n        run: |\n          python -m mypy $MODULE_NAME\n\n      - name: Delete source directory\n        shell: bash\n        run: |\n          rm -rf $MODULE_NAME\n\n      - name: Uninstall all packages\n        run: |\n          python -m pip freeze > installed.txt\n          python -m pip uninstall -y -r installed.txt\n\n      - name: Install from sdist\n        shell: bash\n        run: |\n          SDIST=$(python -c \"import os;print(os.listdir('./dist')[-1])\" 2>&1)\n          python -m pip install dist/$SDIST\n\n      - name: Test import\n        shell: bash\n        run: |\n          python -c \"import $MODULE_NAME\" -Werror\n\n      - name: Install test requirements\n        run: |\n          python -m pip install -U -r requirements.txt\n\n      - name: Run tests\n        shell: bash\n        run: |\n          python -m pytest --pyargs $MODULE_NAME -Werror\n"
  },
  {
    "path": ".gitignore",
    "content": ".vscode/\ntmp/\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# PyCharm\n.idea\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (C) 2018 Ines Montani\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\nall copies 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\nTHE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE\nrecursive-include wasabi/tests/test-data *.ipynb\n"
  },
  {
    "path": "README.md",
    "content": "# wasabi: A lightweight console printing and formatting toolkit\n\nOver the years, I've written countless implementations of coloring and\nformatting utilities to output messages in our libraries like\n[spaCy](https://spacy.io), [Thinc](https://github.com/explosion/thinc) and\n[Prodigy](https://prodi.gy). While there are many other great open-source\noptions, I've always ended up wanting something slightly different or slightly\ncustom.\n\nThis package is still a work in progress and aims to bundle those utilities in a\nstandardised way so they can be shared across our other projects. It's super\nlightweight, has zero dependencies and works with Python 3.6+.\n\n[![tests](https://github.com/explosion/wasabi/actions/workflows/tests.yml/badge.svg)](https://github.com/explosion/wasabi/actions/workflows/tests.yml)\n[![PyPi](https://img.shields.io/pypi/v/wasabi.svg?style=flat-square&logo=pypi&logoColor=white)](https://pypi.python.org/pypi/wasabi)\n[![conda](https://img.shields.io/conda/vn/conda-forge/wasabi.svg?style=flat-square&logo=conda-forge/logoColor=white)](https://anaconda.org/conda-forge/wasabi)\n[![GitHub](https://img.shields.io/github/release/ines/wasabi/all.svg?style=flat-square&logo=github)](https://github.com/ines/wasabi)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/ambv/black)\n\n<img width=\"609\" src=\"https://user-images.githubusercontent.com/13643239/48663861-8c9ea000-ea96-11e8-8b04-d120c52276a8.png\">\n\n## 💬 FAQ\n\n### Are you going to add more features?\n\nYes, there's still a few of helpers and features to port over. However, the new\nfeatures will be heavily biased by what we (think we) need. I always appreciate\npull requests to improve the existing functionality – but I want to keep this\nlibrary as simple, lightweight and specific as possible.\n\n### Can I use this for my projects?\n\nSure, if you like it, feel free to adopt it! Just keep in mind that the package\nis very specific and not intended to be a full-featured and fully customisable\nformatting library. If that's what you're looking for, you might want to try\nother packages – for example, [`colored`](https://pypi.org/project/colored/),\n[`crayons`](https://github.com/kennethreitz/crayons),\n[`colorful`](https://github.com/timofurrer/colorful),\n[`tabulate`](https://bitbucket.org/astanin/python-tabulate),\n[`console`](https://github.com/mixmastamyk/console) or\n[`py-term`](https://github.com/gravmatt/py-term), to name a few.\n\n### Why `wasabi`?\n\nI was looking for a short and descriptive name, but everything was already\ntaken. So I ended up naming this package after one of my rats, Wasabi. 🐀\n\n## ⌛️ Installation\n\n```bash\npip install wasabi\n```\n\n## 🎛 API\n\n### <kbd>function</kbd> `msg`\n\nAn instance of `Printer`, initialized with the default config. Useful as a quick\nshortcut if you don't need to customize initialization.\n\n```python\nfrom wasabi import msg\n\nmsg.good(\"Success!\")\n```\n\n### <kbd>class</kbd> `Printer`\n\n#### <kbd>method</kbd> `Printer.__init__`\n\n```python\nfrom wasabi import Printer\n\nmsg = Printer()\n```\n\n| Argument          | Type      | Description                                                   | Default       |\n| ----------------- | --------- | ------------------------------------------------------------- | ------------- |\n| `pretty`          | bool      | Pretty-print output with colors and icons.                    | `True`        |\n| `no_print`        | bool      | Don't actually print, just return.                            | `False`       |\n| `colors`          | dict      | Add or overwrite color values, names mapped to `0`-`256`.     | `None`        |\n| `icons`           | dict      | Add or overwrite icon. Name mapped to unicode.                | `None`        |\n| `line_max`        | int       | Maximum line length (for divider).                            | `80`          |\n| `animation`       | str       | Steps of loading animation for `Printer.loading`.             | `\"⠙⠹⠸⠼⠴⠦⠧⠇⠏\"` |\n| `animation_ascii` | str       | Alternative animation for ASCII terminals.                    | `\"\\|/-\\\\\"`    |\n| `hide_animation`  | bool      | Don't display animation, e.g. for logs.                       | `False`       |\n| `ignore_warnings` | bool      | Don't output messages of type `MESSAGE.WARN`.                 | `False`       |\n| `env_prefix`      | str       | Prefix for environment variables, e.g. `WASABI_LOG_FRIENDLY`. | `\"WASABI\"`    |\n| `timestamp`       | bool      | Add timestamp before output.                                  | `False`       |\n| **RETURNS**       | `Printer` | The initialized printer.                                      | -             |\n\n#### <kbd>method</kbd> `Printer.text`\n\n```python\nmsg = Printer()\nmsg.text(\"Hello world!\")\n```\n\n| Argument   | Type           | Description                                                                                                            | Default |\n| ---------- | -------------- | ---------------------------------------------------------------------------------------------------------------------- | ------- |\n| `title`    | str            | The main text to print.                                                                                                | `\"\"`    |\n| `text`     | str            | Optional additional text to print.                                                                                     | `\"\"`    |\n| `color`    |  unicode / int | Color name or value.                                                                                                   | `None`  |\n| `icon`     | str            | Name of icon to add.                                                                                                   | `None`  |\n| `show`     | bool           | Whether to print or not. Can be used to only output messages under certain condition, e.g. if `--verbose` flag is set. | `True`  |\n| `spaced`   | bool           | Whether to add newlines around the output.                                                                             | `False` |\n| `no_print` | bool           | Don't actually print, just return. Overwrites global setting.                                                          | `False` |\n| `exits`    | int            | If set, perform a system exit with the given code after printing.                                                      | `None`  |\n\n#### <kbd>method</kbd> `Printer.good`, `Printer.fail`, `Printer.warn`, `Printer.info`\n\nPrint special formatted messages.\n\n```python\nmsg = Printer()\nmsg.good(\"Success\")\nmsg.fail(\"Error\")\nmsg.warn(\"Warning\")\nmsg.info(\"Info\")\n```\n\n| Argument | Type | Description                                                                                                            | Default |\n| -------- | ---- | ---------------------------------------------------------------------------------------------------------------------- | ------- |\n| `title`  | str  | The main text to print.                                                                                                | `\"\"`    |\n| `text`   | str  | Optional additional text to print.                                                                                     | `\"\"`    |\n| `show`   | bool | Whether to print or not. Can be used to only output messages under certain condition, e.g. if `--verbose` flag is set. | `True`  |\n| `exits`  | int  | If set, perform a system exit with the given code after printing.                                                      | `None`  |\n\n#### <kbd>method</kbd> `Printer.divider`\n\nPrint a formatted divider.\n\n```python\nmsg = Printer()\nmsg.divider(\"Heading\")\n```\n\n| Argument | Type | Description                                                                                                            | Default |\n| -------- | ---- | ---------------------------------------------------------------------------------------------------------------------- | ------- |\n| `text`   | str  | Headline text. If empty, only the line is printed.                                                                     | `\"\"`    |\n| `char`   | str  | Single line character to repeat.                                                                                       | `\"=\"`   |\n| `show`   | bool | Whether to print or not. Can be used to only output messages under certain condition, e.g. if `--verbose` flag is set. | `True`  |\n| `icon`   | str  | Optional icon to use with title.                                                                                       | `None`  |\n\n#### <kbd>contextmanager</kbd> `Printer.loading`\n\n```python\nmsg = Printer()\nwith msg.loading(\"Loading...\"):\n    # Do something here that takes longer\n    time.sleep(10)\nmsg.good(\"Successfully loaded something!\")\n```\n\n| Argument | Type | Description                        | Default        |\n| -------- | ---- | ---------------------------------- | -------------- |\n| `text`   | str  | The text to display while loading. | `\"Loading...\"` |\n\n#### <kbd>method</kbd> `Printer.table`, `Printer.row`\n\nSee [Tables](#tables).\n\n#### <kbd>property</kbd> `Printer.counts`\n\nGet the counts of how often the special printers were fired, e.g.\n`MESSAGES.GOOD`. Can be used to print an overview like \"X warnings\"\n\n```python\nmsg = Printer()\nmsg.good(\"Success\")\nmsg.fail(\"Error\")\nmsg.warn(\"Error\")\n\nprint(msg.counts)\n# Counter({'good': 1, 'fail': 2, 'warn': 0, 'info': 0})\n```\n\n| Argument    | Type      | Description                                          |\n| ----------- | --------- | ---------------------------------------------------- |\n| **RETURNS** | `Counter` | The counts for the individual special message types. |\n\n### Tables\n\n#### <kbd>function</kbd> `table`\n\nLightweight helper to format tabular data.\n\n```python\nfrom wasabi import table\n\ndata = [(\"a1\", \"a2\", \"a3\"), (\"b1\", \"b2\", \"b3\")]\nheader = (\"Column 1\", \"Column 2\", \"Column 3\")\nwidths = (8, 9, 10)\naligns = (\"r\", \"c\", \"l\")\nformatted = table(data, header=header, divider=True, widths=widths, aligns=aligns)\n```\n\n```\nColumn 1   Column 2    Column 3\n--------   ---------   ----------\n      a1      a2       a3\n      b1      b2       b3\n```\n\n| Argument       | Type                | Description                                                                                                                         | Default    |\n| -------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------- |\n| `data`         | iterable / dict     | The data to render. Either a list of lists (one per row) or a dict for two-column tables.                                           |            |\n| `header`       | iterable            | Optional header columns.                                                                                                            | `None`     |\n| `footer`       | iterable            | Optional footer columns.                                                                                                            | `None`     |\n| `divider`      | bool                | Show a divider line between header/footer and body.                                                                                 | `False`    |\n| `widths`       | iterable / `\"auto\"` | Column widths in order. If `\"auto\"`, widths will be calculated automatically based on the largest value.                            | `\"auto\"`   |\n| `max_col`      | int                 | Maximum column width.                                                                                                               | `30`       |\n| `spacing`      | int                 | Number of spaces between columns.                                                                                                   | `3`        |\n| `aligns`       | iterable / unicode  | Columns alignments in order. `\"l\"` (left, default), `\"r\"` (right) or `\"c\"` (center). If If a string, value is used for all columns. | `None`     |\n| `multiline`    | bool                | If a cell value is a list of a tuple, render it on multiple lines, with one value per line.                                         | `False`    |\n| `env_prefix`   | unicode             | Prefix for environment variables, e.g. WASABI_LOG_FRIENDLY.                                                                         | `\"WASABI\"` |\n| `color_values` | dict                | Add or overwrite color values, name mapped to value.                                                                                | `None`     |\n| `fg_colors`    | iterable            | Foreground colors, one per column. None can be specified for individual columns to retain the default background color.             | `None`     |\n| `bg_colors`    | iterable            | Background colors, one per column. None can be specified for individual columns to retain the default background color.             | `None`     |\n| **RETURNS**    | str                 | The formatted table.                                                                                                                |            |\n\n#### <kbd>function</kbd> `row`\n\n```python\nfrom wasabi import row\n\ndata = (\"a1\", \"a2\", \"a3\")\nformatted = row(data)\n```\n\n```\na1   a2   a3\n```\n\n| Argument     | Type                  | Description                                                                                                                                                | Default    |\n| ------------ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |\n| `data`       | iterable              | The individual columns to format.                                                                                                                          |            |\n| `widths`     | list / int / `\"auto\"` | Column widths, either one integer for all columns or an iterable of values. If \"auto\", widths will be calculated automatically based on the largest value. | `\"auto\"`   |\n| `spacing`    | int                   | Number of spaces between columns.                                                                                                                          | `3`        |\n| `aligns`     | list                  | Columns alignments in order. `\"l\"` (left), `\"r\"` (right) or `\"c\"` (center).                                                                                | `None`     |\n| `env_prefix` | unicode               | Prefix for environment variables, e.g. WASABI_LOG_FRIENDLY.                                                                                                | `\"WASABI\"` |\n| `fg_colors`  | list                  | Foreground colors for the columns, in order. None can be specified for individual columns to retain the default foreground color.                          | `None`     |\n| `bg_colors`  | list                  | Background colors for the columns, in order. None can be specified for individual columns to retain the default background color.                          | `None`     |\n| **RETURNS**  | str                   | The formatted row.                                                                                                                                         |            |\n\n### <kbd>class</kbd> `TracebackPrinter`\n\nHelper to output custom formatted tracebacks and error messages. Currently used\nin [Thinc](https://github.com/explosion/thinc).\n\n#### <kbd>method</kbd> `TracebackPrinter.__init__`\n\nInitialize a traceback printer.\n\n```python\nfrom wasabi import TracebackPrinter\n\ntb = TracebackPrinter(tb_base=\"thinc\", tb_exclude=(\"check.py\",))\n```\n\n| Argument          | Type               | Description                                                                                                                                                              | Default    |\n| ----------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |\n| `color_error`     | str / int          | Color name or code for errors (passed to `color` helper).                                                                                                                | `\"red\"`    |\n| `color_tb`        | str / int          | Color name or code for traceback headline (passed to `color` helper).                                                                                                    | `\"blue\"`   |\n| `color_highlight` | str / int          | Color name or code for highlighted text (passed to `color` helper).                                                                                                      | `\"yellow\"` |\n| `indent`          | int                | Number of spaces to use for indentation.                                                                                                                                 | `2`        |\n| `tb_base`         | str                | Name of directory to use to show relative paths. For example, `\"thinc\"` will look for the last occurence of `\"/thinc/\"` in a path and only show path to the right of it. | `None`     |\n| `tb_exclude`      | tuple              | List of filenames to exclude from traceback.                                                                                                                             | `tuple()`  |\n| **RETURNS**       | `TracebackPrinter` | The traceback printer.                                                                                                                                                   |            |\n\n#### <kbd>method</kbd> `TracebackPrinter.__call__`\n\nOutput custom formatted tracebacks and errors.\n\n```python\nfrom wasabi import TracebackPrinter\nimport traceback\n\ntb = TracebackPrinter(tb_base=\"thinc\", tb_exclude=(\"check.py\",))\n\nerror = tb(\"Some error\", \"Error description\", highlight=\"kwargs\", tb=traceback.extract_stack())\nraise ValueError(error)\n```\n\n```\n  Some error\n  Some error description\n\n  Traceback:\n  ├─ <lambda> [61] in .env/lib/python3.6/site-packages/pluggy/manager.py\n  ├─── _multicall [187] in .env/lib/python3.6/site-packages/pluggy/callers.py\n  └───── pytest_fixture_setup [969] in .env/lib/python3.6/site-packages/_pytest/fixtures.py\n         >>> result = call_fixture_func(fixturefunc, request, kwargs)\n```\n\n| Argument    | Type     | Description                                                                                | Default |\n| ----------- | -------- | ------------------------------------------------------------------------------------------ | ------- |\n| `title`     | str      | The message title.                                                                         |         |\n| `*texts`    | str      | Optional texts to print (one per line).                                                    |         |\n| `highlight` | str      | Optional sequence to highlight in the traceback, e.g. the bad value that caused the error. | `False` |\n| `tb`        | iterable | The traceback, e.g. generated by `traceback.extract_stack()`.                              | `None`  |\n| **RETURNS** | str      | The formatted traceback. Can be printed or raised by custom exception.                     |         |\n\n### <kbd>class</kbd> `MarkdownRenderer`\n\nHelper to create Markdown-formatted content. Will store the blocks added to the\nMarkdown document in order.\n\n```python\nfrom wasabi import MarkdownRenderer\n\nmd = MarkdownRenderer()\nmd.add(md.title(1, \"Hello world\"))\nmd.add(\"This is a paragraph\")\nprint(md.text)\n```\n\n### <kbd>method</kbd> `MarkdownRenderer.__init__`\n\nInitialize a Markdown renderer.\n\n```python\nfrom wasabi import MarkdownRenderer\n\nmd = MarkdownRenderer()\n```\n\n| Argument    | Type               | Description                    | Default |\n| ----------- | ------------------ | ------------------------------ | ------- |\n| `no_emoji`  | bool               | Don't include emoji in titles. | `False` |\n| **RETURNS** | `MarkdownRenderer` | The renderer.                  |\n\n### <kbd>method</kbd> `MarkdownRenderer.add`\n\nAdd a block to the Markdown document.\n\n```python\nfrom wasabi import MarkdownRenderer\n\nmd = MarkdownRenderer()\nmd.add(\"This is a paragraph\")\n```\n\n| Argument | Type | Description         | Default |\n| -------- | ---- | ------------------- | ------- |\n| `text`   | str  | The content to add. |         |\n\n### <kbd>property</kbd> `MarkdownRenderer.text`\n\nThe rendered Markdown document.\n\n```python\nmd = MarkdownRenderer()\nmd.add(\"This is a paragraph\")\nprint(md.text)\n```\n\n| Argument    | Type | Description                      | Default |\n| ----------- | ---- | -------------------------------- | ------- |\n| **RETURNS** | str  | The document as a single string. |         |\n\n### <kbd>method</kbd> `MarkdownRenderer.table`\n\nCreate a Markdown-formatted table.\n\n```python\nmd = MarkdownRenderer()\ntable = md.table([(\"a\", \"b\"), (\"c\", \"d\")], [\"Column 1\", \"Column 2\"])\nmd.add(table)\n```\n\n<!-- prettier-ignore -->\n```markdown\n| Column 1 | Column 2 |\n| --- | --- |\n| a | b |\n| c | d |\n```\n\n| Argument    | Type                    | Description                                                                          | Default |\n| ----------- | ----------------------- | ------------------------------------------------------------------------------------ | ------- |\n| `data`      | Iterable[Iterable[str]] | The body, one iterable per row, containig an interable of column contents.           |         |\n| `header`    | Iterable[str]           | The column names.                                                                    |         |\n| `aligns`    | Iterable[str]           | Columns alignments in order. `\"l\"` (left, default), `\"r\"` (right) or `\"c\"` (center). | `None`  |\n| **RETURNS** | str                     | The table.                                                                           |         |\n\n### <kbd>method</kbd> `MarkdownRenderer.title`\n\nCreate a Markdown-formatted heading.\n\n```python\nmd = MarkdownRenderer()\nmd.add(md.title(1, \"Hello world\"))\nmd.add(md.title(2, \"Subheading\", \"💖\"))\n```\n\n```markdown\n# Hello world\n\n## 💖 Subheading\n```\n\n| Argument    | Type | Description                            | Default |\n| ----------- | ---- | -------------------------------------- | ------- |\n| `level`     | int  | The heading level, e.g. `3` for `###`. |         |\n| `text`      | str  | The heading text.                      |         |\n| `emoji`     | str  | Optional emoji to show before heading. | `None`  |\n| **RETURNS** | str  | The rendered title.                    |         |\n\n### <kbd>method</kbd> `MarkdownRenderer.list`\n\nCreate a Markdown-formatted non-nested list.\n\n```python\nmd = MarkdownRenderer()\nmd.add(md.list([\"item\", \"other item\"]))\nmd.add(md.list([\"first item\", \"second item\"], numbered=True))\n```\n\n```markdown\n- item\n- other item\n\n1. first item\n2. second item\n```\n\n| Argument    | Type          | Description                     | Default |\n| ----------- | ------------- | ------------------------------- | ------- |\n| `items`     | Iterable[str] | The list items.                 |         |\n| `numbered`  | bool          | Whether to use a numbered list. | `False` |\n| **RETURNS** | str           | The rendered list.              |         |\n\n### <kbd>method</kbd> `MarkdownRenderer.link`\n\nCreate a Markdown-formatted link.\n\n```python\nmd = MarkdownRenderer()\nmd.add(md.link(\"Google\", \"https://google.com\"))\n```\n\n```markdown\n[Google](https://google.com)\n```\n\n| Argument    | Type | Description        | Default |\n| ----------- | ---- | ------------------ | ------- |\n| `text`      | str  | The link text.     |         |\n| `url`       | str  | The link URL.      |         |\n| **RETURNS** | str  | The rendered link. |         |\n\n### <kbd>method</kbd> `MarkdownRenderer.code_block`\n\nCreate a Markdown-formatted code block.\n\n```python\nmd = MarkdownRenderer()\nmd.add(md.code_block(\"import spacy\", \"python\"))\n```\n\n````markdown\n```python\nimport spacy\n```\n````\n\n| Argument    | Type | Description              | Default |\n| ----------- | ---- | ------------------------ | ------- |\n| `text`      | str  | The code text.           |         |\n| `lang`      | str  | Optional code language.  | `\"\"`    |\n| **RETURNS** | str  | The rendered code block. |         |\n\n### <kbd>method</kbd> `MarkdownRenderer.code`, `MarkdownRenderer.bold`, `MarkdownRenderer.italic`\n\nCreate a Markdown-formatted text.\n\n```python\nmd = MarkdownRenderer()\nmd.add(md.code(\"import spacy\"))\nmd.add(md.bold(\"Hello!\"))\nmd.add(md.italic(\"Emphasis\"))\n```\n\n```markdown\n`import spacy`\n\n**Hello!**\n\n_Emphasis_\n```\n\n### Utilities\n\n#### <kbd>function</kbd> `color`\n\n```python\nfrom wasabi import color\n\nformatted = color(\"This is a text\", fg=\"white\", bg=\"green\", bold=True)\n```\n\n| Argument    | Type      | Description                                   | Default |\n| ----------- | --------- | --------------------------------------------- | ------- |\n| `text`      | str       | The text to be formatted.                     | -       |\n| `fg`        | str / int | Foreground color. String name or `0` - `256`. | `None`  |\n| `bg`        | str / int | Background color. String name or `0` - `256`. | `None`  |\n| `bold`      | bool      | Format the text in bold.                      | `False` |\n| `underline` | bool      | Format the text by underlining.               | `False` |\n| **RETURNS** | str       | The formatted string.                         |         |\n\n#### <kbd>function</kbd> `wrap`\n\n```python\nfrom wasabi import wrap\n\nwrapped = wrap(\"Hello world, this is a text.\", indent=2)\n```\n\n| Argument    | Type | Description                                | Default |\n| ----------- | ---- | ------------------------------------------ | ------- |\n| `text`      | str  | The text to wrap.                          | -       |\n| `wrap_max`  | int  | Maximum line width, including indentation. | `80`    |\n| `indent`    | int  | Number of spaces used for indentation.     | `4`     |\n| **RETURNS** | str  | The wrapped text with line breaks.         |         |\n\n#### <kbd>function</kbd> `diff_strings`\n\n```python\nfrom wasabi import diff_strings\n\ndiff = diff_strings(\"hello world!\", \"helloo world\")\n```\n\n| Argument    | Type      | Description                                                                  | Default            |\n| ----------- | --------- | ---------------------------------------------------------------------------- | ------------------ |\n| `a`         | str       | The first string to diff.                                                    |\n| `b`         | str       | The second string to diff.                                                   |\n| `fg`        | str / int | Foreground color. String name or `0` - `256`.                                | `\"black\"`          |\n| `bg`        | tuple     | Background colors as `(insert, delete)` tuple of string name or `0` - `256`. | `(\"green\", \"red\")` |\n| **RETURNS** | str       | The formatted diff.                                                          |                    |\n\n### Environment variables\n\nWasabi also respects the following environment variables. The prefix can be\ncustomised on the `Printer` via the `env_prefix` argument. For example, setting\n`env_prefix=\"SPACY\"` will expect the environment variable `SPACY_LOG_FRIENDLY`.\n\n| Name                   | Description                                            |\n| ---------------------- | ------------------------------------------------------ |\n| `ANSI_COLORS_DISABLED` | Disable colors.                                        |\n| `WASABI_LOG_FRIENDLY`  | Make output nicer for logs (no colors, no animations). |\n| `WASABI_NO_PRETTY`     | Disable pretty printing, e.g. colors and icons.        |\n\n## 🔔 Run tests\n\nFork or clone the repo, make sure you have `pytest` installed and then run it on\nthe package directory. The tests are located in\n[`/wasabi/tests`](/wasabi/tests).\n\n```bash\npip install pytest\ncd wasabi\npython -m pytest wasabi\n```\n"
  },
  {
    "path": "requirements.txt",
    "content": "typing_extensions>=3.7.4.1,<5.0.0; python_version < \"3.8\"\n# Development dependencies\npytest\nmypy\ntypes-colorama\nnbconvert\nipykernel\n"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\nversion = 1.1.3\ndescription = A lightweight console printing and formatting toolkit\nurl = https://github.com/explosion/wasabi\nauthor = Explosion\nauthor_email = contact@explosion.ai\nlicense = MIT\nlong_description = file: README.md\nlong_description_content_type = text/markdown\n\n[options]\nzip_safe = true\ninclude_package_data = true\npython_requires = >=3.6\ninstall_requires =\n    colorama >= 0.4.6; sys_platform == \"win32\" and python_version >= \"3.7\"\n    typing_extensions>=3.7.4.1,<5.0.0; python_version < \"3.8\"\n\n[flake8]\nignore = E203, E266, E501, E731, W503, E741\nmax-line-length = 80\nselect = B,C,E,F,W,T4,B9\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python\n\nif __name__ == \"__main__\":\n    from setuptools import setup, find_packages\n\n    setup(name=\"wasabi\", packages=find_packages())\n"
  },
  {
    "path": "wasabi/__init__.py",
    "content": "from .markdown import MarkdownRenderer  # noqa\nfrom .printer import Printer  # noqa\nfrom .tables import row, table  # noqa\nfrom .traceback_printer import TracebackPrinter  # noqa\nfrom .util import MESSAGES  # noqa\nfrom .util import color, diff_strings, format_repr, get_raw_input, wrap  # noqa\n\nmsg = Printer()\n\n# fmt: off\n__all__ = [\n    \"color\",\n    \"diff_strings\",\n    \"format_repr\",\n    \"get_raw_input\",\n    \"msg\",\n    \"row\",\n    \"table\",\n    \"wrap\",\n    \"MarkdownRenderer\",\n    \"MESSAGES\",\n    \"Printer\",\n    \"TracebackPrinter\",\n]\n# fmt: on\n"
  },
  {
    "path": "wasabi/compat.py",
    "content": "import sys\n\n\n# Use typing_extensions for Python versions < 3.8\nif sys.version_info < (3, 8):\n    from typing_extensions import Protocol, Literal\nelse:\n    from typing import Protocol, Literal  # noqa: F401\n"
  },
  {
    "path": "wasabi/markdown.py",
    "content": "from typing import Iterable, List, Optional, Sequence\n\nfrom .compat import Literal\n\n\nclass MarkdownRenderer:\n    \"\"\"Simple helper for generating raw Markdown.\"\"\"\n\n    def __init__(self, no_emoji: bool = False):\n        \"\"\"Initialize the renderer.\n\n        no_emoji (bool): Don't show emoji in titles etc.\n        \"\"\"\n        self.data: List = []\n        self.no_emoji = no_emoji\n\n    @property\n    def text(self) -> str:\n        \"\"\"RETURNS (str): The Markdown document.\"\"\"\n        return \"\\n\\n\".join(self.data)\n\n    def add(self, content: str):\n        \"\"\"Add a string to the Markdown document.\n\n        content (str): Add content to the document.\n        \"\"\"\n        self.data.append(content)\n\n    def table(\n        self,\n        data: Iterable[Iterable[str]],\n        header: Sequence[str],\n        aligns: Optional[Sequence[Literal[\"r\", \"c\", \"l\"]]] = None,\n    ) -> str:\n        \"\"\"Create a Markdown table.\n\n        data (Iterable[Iterable[str]]): The body, one iterable per row,\n            containig an interable of column contents.\n        header (Sequence[str]): The column names.\n        aligns (Optional[Sequence[Literal[\"r\", \"c\", \"l\"]]]): Optional alignment-mode for each column. Values should\n            either be 'l' (left), 'r' (right), or 'c' (center). Optional.\n        RETURNS (str): The rendered table.\n        \"\"\"\n        if aligns is None:\n            aligns = [\"l\"] * len(header)\n        if len(aligns) != len(header):\n            err = \"Invalid aligns: {} (header length: {})\".format(aligns, len(header))\n            raise ValueError(err)\n        get_divider = lambda a: \":---:\" if a == \"c\" else \"---:\" if a == \"r\" else \"---\"\n        head = \"| {} |\".format(\" | \".join(header))\n        divider = \"| {} |\".format(\n            \" | \".join(get_divider(aligns[i]) for i in range(len(header)))\n        )\n        body = \"\\n\".join(\"| {} |\".format(\" | \".join(row)) for row in data)\n        return \"{}\\n{}\\n{}\".format(head, divider, body)\n\n    def title(self, level: int, text: str, emoji: Optional[str] = None) -> str:\n        \"\"\"Create a Markdown heading.\n\n        level (int): The heading level, e.g. 3 for ###\n        text (str): The heading text.\n        emoji (Optional[str]): Optional emoji to show before heading text, if enabled.\n        RETURNS (str): The rendered title.\n        \"\"\"\n        prefix = \"{} \".format(emoji) if emoji and not self.no_emoji else \"\"\n        return \"{} {}{}\".format(\"#\" * level, prefix, text)\n\n    def list(self, items: Iterable[str], numbered: bool = False) -> str:\n        \"\"\"Create a non-nested list.\n\n        items (Iterable[str]): The list items.\n        numbered (bool): Whether to use a numbered list.\n        RETURNS (str): The rendered list.\n        \"\"\"\n        content = []\n        for i, item in enumerate(items):\n            if numbered:\n                content.append(\"{}. {}\".format(i + 1, item))\n            else:\n                content.append(\"- {}\".format(item))\n        return \"\\n\".join(content)\n\n    def link(self, text: str, url: str) -> str:\n        \"\"\"Create a Markdown link.\n\n        text (str): The link text.\n        url (str): The link URL.\n        RETURNS (str): The rendered link.\n        \"\"\"\n        return \"[{}]({})\".format(text, url)\n\n    def code_block(self, text: str, lang: str = \"\") -> str:\n        \"\"\"Create a Markdown code block.\n\n        text (str): The code text.\n        lang (str): Optional code language.\n        RETURNS (str): The rendered code block.\n        \"\"\"\n        return \"```{}\\n{}\\n```\".format(lang, text)\n\n    def code(self, text: str) -> str:\n        \"\"\"Create Markdown inline code.\n\n        text (str): The inline code text.\n        RETURNS (str): The rendered code text.\n        \"\"\"\n        return self._wrap(text, \"`\")\n\n    def bold(self, text: str) -> str:\n        \"\"\"Create bold text.\n\n        text (str): The text to format in boldface.\n        RETURNS (str): The formatted text.\n        \"\"\"\n        return self._wrap(text, \"**\")\n\n    def italic(self, text: str):\n        \"\"\"Create italic text.\n\n        text (str): The text to italicize.\n        RETURNS (str): The formatted text.\n        \"\"\"\n        return self._wrap(text, \"_\")\n\n    def _wrap(self, text, marker):\n        return \"{}{}{}\".format(marker, text, marker)\n"
  },
  {
    "path": "wasabi/printer.py",
    "content": "import datetime\nimport itertools\nimport os\nimport sys\nimport time\nimport traceback\nfrom collections import Counter\nfrom contextlib import contextmanager\nfrom multiprocessing import Process\nfrom typing import Any, Collection, Dict, NoReturn, Optional, Union, cast, overload\n\nfrom .compat import Literal\nfrom .tables import row, table\nfrom .util import COLORS, ICONS, MESSAGES, can_render\nfrom .util import color as _color\nfrom .util import locale_escape, supports_ansi, wrap\n\n\nclass Printer(object):\n    def __init__(\n        self,\n        pretty: bool = True,\n        no_print: bool = False,\n        colors: Optional[Dict] = None,\n        icons: Optional[Dict] = None,\n        line_max: int = 80,\n        animation: str = \"⠙⠹⠸⠼⠴⠦⠧⠇⠏\",\n        animation_ascii: str = \"|/-\\\\\",\n        hide_animation: bool = False,\n        ignore_warnings: bool = False,\n        env_prefix: str = \"WASABI\",\n        timestamp: bool = False,\n    ):\n        \"\"\"Initialize the command-line printer.\n\n        pretty (bool): Pretty-print output (colors, icons).\n        no_print (bool): Don't actually print, just return.\n        colors (Optional[Dict]): Optional color values to add or overwrite, name mapped to value.\n        icons (Optional[Dict]): Optional icons to add or overwrite. Name mapped to unicode icon.\n        line_max (int): Maximum line length (for divider).\n        animation (str): Steps of loading animation for loading() method.\n        animation_ascii (str): Alternative animation for ASCII terminals.\n        hide_animation (bool): Don't display animation, e.g. for logs.\n        ignore_warnings (bool): Do not output messages of type MESSAGE.WARN.\n        env_prefix (str): Prefix for environment variables, e.g.\n            WASABI_LOG_FRIENDLY.\n        timestamp (bool): Print a timestamp (default False).\n        RETURNS (Printer): The initialized printer.\n        \"\"\"\n        env_log_friendly = os.getenv(\"{}_LOG_FRIENDLY\".format(env_prefix), False)\n        env_no_pretty = os.getenv(\"{}_NO_PRETTY\".format(env_prefix), False)\n        self._counts: Counter = Counter()\n        self.pretty = pretty and not env_no_pretty\n        self.no_print = no_print\n        self.show_color = supports_ansi() and not env_log_friendly\n        self.hide_animation = hide_animation or env_log_friendly\n        self.ignore_warnings = ignore_warnings\n        self.line_max = line_max\n        self.colors = dict(COLORS)\n        self.icons = dict(ICONS)\n        self.timestamp = timestamp\n        if colors:\n            self.colors.update(colors)\n        if icons:\n            self.icons.update(icons)\n        self.anim = animation if can_render(animation) else animation_ascii\n\n    @property\n    def counts(self) -> Counter:\n        \"\"\"Get the counts of how often the special printers were fired,\n        e.g. MESSAGES.GOOD. Can be used to print an overview like \"X warnings\".\n        \"\"\"\n        return self._counts\n\n    def good(\n        self,\n        title: Any = \"\",\n        text: Any = \"\",\n        show: bool = True,\n        spaced: bool = False,\n        exits: Optional[int] = None,\n    ):\n        \"\"\"Print a success message.\n\n        title (Any): The main text to print.\n        text (Any): Optional additional text to print.\n        show (bool): Whether to print or not. Can be used to only output\n            messages under certain condition, e.g. if --verbose flag is set.\n        spaced (bool): Whether to add newlines around the output.\n        exits (Optional[int]): Optional toggle to perform a system exit.\n        \"\"\"\n        return self._get_msg(\n            title, text, style=MESSAGES.GOOD, show=show, spaced=spaced, exits=exits\n        )\n\n    @overload\n    def fail(\n        self,\n        title: Any = \"\",\n        text: Any = \"\",\n        show: bool = True,\n        spaced: bool = False,\n        exits: Optional[Literal[0, False]] = None,\n    ):\n        ...\n\n    @overload\n    def fail(\n        self,\n        title: Any = \"\",\n        text: Any = \"\",\n        show: bool = True,\n        spaced: bool = False,\n        exits: Literal[1, True] = True,\n    ) -> NoReturn:\n        ...\n\n    def fail(\n        self,\n        title: Any = \"\",\n        text: Any = \"\",\n        show: bool = True,\n        spaced: bool = False,\n        exits: Optional[Union[int, bool]] = None,\n    ) -> Union[str, None, NoReturn]:\n        \"\"\"Print an error message.\n\n        title (Any): The main text to print.\n        text (Any): Optional additional text to print.\n        show (bool): Whether to print or not. Can be used to only output\n            messages under certain condition, e.g. if --verbose flag is set.\n        spaced (bool): Whether to add newlines around the output.\n        exits (Optional[int]): Optional toggle to perform a system exit.\n        \"\"\"\n        return self._get_msg(\n            title, text, style=MESSAGES.FAIL, show=show, spaced=spaced, exits=exits\n        )\n\n    def warn(\n        self,\n        title: Any = \"\",\n        text: Any = \"\",\n        show: bool = True,\n        spaced: bool = False,\n        exits: Optional[int] = None,\n    ):\n        \"\"\"Print a warning message.\n\n        title (Any): The main text to print.\n        text (Any): Optional additional text to print.\n        show (bool): Whether to print or not. Can be used to only output\n            messages under certain condition, e.g. if --verbose flag is set.\n        spaced (bool): Whether to add newlines around the output.\n        exits (Optional[int]): Optional toggle to perform a system exit.\n        \"\"\"\n        return self._get_msg(\n            title, text, style=MESSAGES.WARN, show=show, spaced=spaced, exits=exits\n        )\n\n    def info(\n        self,\n        title: Any = \"\",\n        text: Any = \"\",\n        show: bool = True,\n        spaced: bool = False,\n        exits: Optional[int] = None,\n    ):\n        \"\"\"Print an informational message.\n\n        title (Any): The main text to print.\n        text (Any): Optional additional text to print.\n        show (bool): Whether to print or not. Can be used to only output\n            messages under certain condition, e.g. if --verbose flag is set.\n        spaced (bool): Whether to add newlines around the output.\n        exits (Optional[int]): Optional toggle to perform a system exit.\n        \"\"\"\n        return self._get_msg(\n            title, text, style=MESSAGES.INFO, show=show, spaced=spaced, exits=exits\n        )\n\n    def text(\n        self,\n        title: Any = \"\",\n        text: Any = \"\",\n        color: Optional[Union[str, int]] = None,\n        bg_color: Optional[Union[str, int]] = None,\n        icon: Optional[str] = None,\n        spaced: bool = False,\n        show: bool = True,\n        no_print: bool = False,\n        exits: Optional[int] = None,\n    ):\n        \"\"\"Print a message.\n\n        title (Any): The main text to print.\n        text (Any): Optional additional text to print.\n        color (Optional[Union[str, int]]): Optional foreground color.\n        bg_color (Optional[Union[str, int]]): Optional background color.\n        icon (Optional[str]): Optional name of icon to add.\n        spaced (bool): Whether to add newlines around the output.\n        show (bool): Whether to print or not. Can be used to only output\n            messages under certain condition, e.g. if --verbose flag is set.\n        no_print (bool): Don't actually print, just return.\n        exits (Optional[int]): Perform a system exit. Optional.\n        \"\"\"\n        if not show:\n            return\n        if self.pretty:\n            color = self.colors.get(cast(str, color), color)\n            bg_color = self.colors.get(cast(str, bg_color), bg_color)\n            icon = self.icons.get(cast(str, icon))\n            if icon:\n                title = locale_escape(\"{} {}\".format(icon, title)).strip()\n            if self.show_color:\n                title = _color(title, fg=color, bg=bg_color)\n            title = wrap(title, indent=0)\n        if text:\n            title = \"{}\\n{}\".format(title, wrap(text, indent=0))\n        if self.timestamp:\n            now = datetime.datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n            title = \"{}\\t{}\".format(now, title)\n        if exits is not None or spaced:\n            title = \"\\n{}\\n\".format(title)\n        if not self.no_print and not no_print:\n            print(title)\n        if exits is not None:\n            sys.stdout.flush()\n            sys.stderr.flush()\n            if self.no_print or no_print and exits != 0:\n                try:\n                    raise RuntimeError(title.strip())\n                except Exception as e:\n                    # Remove wasabi from the traceback and re-raise\n                    tb = \"\\n\".join(traceback.format_stack()[:-3])\n                    raise SystemExit(\"{}\\n{}\".format(tb, e))\n            sys.exit(exits)\n        if self.no_print or no_print:\n            return title\n\n    def divider(\n        self,\n        text: str = \"\",\n        char: str = \"=\",\n        show: bool = True,\n        icon: Optional[str] = None,\n    ):\n        \"\"\"Print a divider with a headline:\n        ============================ Headline here ===========================\n\n        text (str): Headline text. If empty, only the line is printed.\n        char (str): Line character to repeat, e.g. =.\n        show (bool): Whether to print or not.\n        icon (Optional[str]): Optional icon to display with title.\n        \"\"\"\n        if not show:\n            return\n        if len(char) != 1:\n            raise ValueError(\n                \"Divider chars need to be one character long. \"\n                \"Received: {}\".format(char)\n            )\n        if self.pretty:\n            icon = self.icons.get(cast(str, icon))\n            if icon:\n                text = locale_escape(\"{} {}\".format(icon, text)).strip()\n            deco = char * (int(round((self.line_max - len(text))) / 2) - 2)\n            text = \" {} \".format(text) if text else \"\"\n            text = _color(\n                \"\\n{deco}{text}{deco}\".format(deco=deco, text=text), bold=True\n            )\n            if len(text) < self.line_max:\n                text = text + char * (self.line_max - len(text))\n        if self.no_print:\n            return text\n        print(text)\n\n    def table(self, data: Union[Collection, Dict], **kwargs):\n        \"\"\"Print data as a table.\n\n        data (Union[Collection, Dict]): The data to render. Either a list of lists\n            (one per row) or a dict for two-column tables.\n        kwargs: Table settings. See tables.table for details.\n        \"\"\"\n        title = kwargs.pop(\"title\", None)\n        text = table(data, **kwargs)\n        if title:\n            self.divider(title)\n        if self.no_print:\n            return text\n        print(text)\n\n    def row(self, data: Collection, **kwargs):\n        \"\"\"Print a table row.\n\n        data (Collection): The individual columns to format.\n        kwargs: Row settings. See tables.row for details.\n        \"\"\"\n        text = row(data, **kwargs)\n        if self.no_print:\n            return text\n        print(text)\n\n    @contextmanager\n    def loading(self, text: str = \"Loading...\"):\n        if self.no_print:\n            yield\n        elif self.hide_animation:\n            print(text)\n            yield\n        else:\n            sys.stdout.flush()\n            t = Process(target=self._spinner, args=(text,))\n            t.start()\n            try:\n                yield\n            except Exception as e:\n                # Handle exception inside the with block\n                t.terminate()\n                sys.stdout.write(\"\\n\")\n                raise (e)\n            t.terminate()\n            sys.stdout.write(\"\\r\\x1b[2K\")  # erase line\n            sys.stdout.flush()\n\n    def _spinner(self, text: str = \"Loading...\"):\n        for char in itertools.cycle(self.anim):\n            sys.stdout.write(\"\\r{} {}\".format(char, text))\n            sys.stdout.flush()\n            time.sleep(0.1)\n\n    def _get_msg(\n        self,\n        title: Any,\n        text: Any,\n        style: Optional[str] = None,\n        show: bool = False,\n        spaced: bool = False,\n        exits: Optional[int] = None,\n    ):\n        if self.ignore_warnings and style == MESSAGES.WARN:\n            show = False\n        self._counts[style] += 1\n        return self.text(\n            title, text, color=style, icon=style, show=show, spaced=spaced, exits=exits\n        )\n"
  },
  {
    "path": "wasabi/py.typed",
    "content": ""
  },
  {
    "path": "wasabi/tables.py",
    "content": "import os\nfrom itertools import zip_longest\nfrom typing import Collection, Dict, Iterable, List, Optional, Sequence, Union\nfrom typing import cast\n\nfrom .compat import Literal\nfrom .util import COLORS\nfrom .util import color as _color\nfrom .util import supports_ansi\n\nALIGN_MAP = {\"l\": \"<\", \"r\": \">\", \"c\": \"^\"}\n\n\ndef table(\n    # fmt: off\n    data: Union[Collection, Dict],\n    header: Optional[Iterable] = None,\n    footer: Optional[Iterable] = None,\n    divider: bool = False,\n    widths: Union[Iterable[int], Literal[\"auto\"]] = \"auto\",\n    max_col: int = 30,\n    spacing: int = 3,\n    aligns: Optional[Union[Iterable[Literal[\"r\", \"c\", \"l\"]], Literal[\"r\", \"c\", \"l\"]]] = None,\n    multiline: bool = False,\n    env_prefix: str = \"WASABI\",\n    color_values: Optional[Dict] = None,\n    fg_colors: Optional[Iterable] = None,\n    bg_colors: Optional[Iterable] = None,\n    # fmt: on\n) -> str:\n    \"\"\"Format tabular data.\n\n    data (Union[Collection, Dict]): The data to render. Either a list of lists (one per\n        row) or a dict for two-column tables.\n    header (Optional[Iterable]): Optional header columns.\n    footer (Optional[Iterable]): Optional footer columns.\n    divider (bool): Show a divider line between header/footer and body.\n    widths (Union[Iterable[int], Literal['auto']]): Column widths in order. If \"auto\", widths\n        will be calculated automatically based on the largest value.\n    max_col (int): Maximum column width.\n    spacing (int): Spacing between columns, in spaces.\n    aligns (Optional[Union[Iterable[str], str]]): Optional column alignments\n        in order. 'l' (left, default), 'r' (right) or 'c' (center). If a string,\n        value is used for all columns.\n    multiline (bool): If a cell value is a list of a tuple, render it on\n        multiple lines, with one value per line.\n    env_prefix (str): Prefix for environment variables, e.g.\n        WASABI_LOG_FRIENDLY.\n    color_values (Optional[Dict]): Optional color values to add or overwrite, name mapped to value.\n    fg_colors (Optional[Iterable]): Optional foreground colors, one per column. None can be specified\n        for individual columns to retain the default foreground color.\n    bg_colors (Optional[Iterable]): Optional background colors, one per column. None can be specified\n        for individual columns to retain the default background color.\n    RETURNS (str): The formatted table.\n    \"\"\"\n    if fg_colors is not None or bg_colors is not None:\n        colors = dict(COLORS)\n        if color_values is not None:\n            colors.update(color_values)\n        if fg_colors is not None:\n            fg_colors = [colors.get(fg_color, fg_color) for fg_color in fg_colors]\n        if bg_colors is not None:\n            bg_colors = [colors.get(bg_color, bg_color) for bg_color in bg_colors]\n    if isinstance(data, dict):\n        data = list(data.items())\n    if multiline:\n        zipped_data = []\n        for i, item in enumerate(data):\n            vals = [v if isinstance(v, (list, tuple)) else [v] for v in item]\n            zipped_data.extend(list(zip_longest(*vals, fillvalue=\"\")))\n            if i < len(data) - 1:\n                zipped_data.append(tuple([\"\" for i in item]))\n        data = zipped_data\n    if widths == \"auto\":\n        widths = _get_max_widths(data, header, footer, max_col)\n    settings = {\n        \"widths\": widths,\n        \"spacing\": spacing,\n        \"aligns\": aligns,\n        \"env_prefix\": env_prefix,\n        \"fg_colors\": fg_colors,\n        \"bg_colors\": bg_colors,\n    }\n    divider_row = row([\"-\" * width for width in widths], **settings)  # type: ignore\n    rows = []\n    if header:\n        rows.append(row(header, **settings))  # type: ignore\n        if divider:\n            rows.append(divider_row)\n    for i, item in enumerate(data):\n        rows.append(row(item, **settings))  # type: ignore\n    if footer:\n        if divider:\n            rows.append(divider_row)\n        rows.append(row(footer, **settings))  # type: ignore\n    return \"\\n{}\\n\".format(\"\\n\".join(rows))\n\n\ndef row(\n    data: Collection,\n    widths: Union[Sequence[int], int, Literal[\"auto\"]] = \"auto\",\n    spacing: int = 3,\n    aligns: Optional[Union[Sequence[Literal[\"r\", \"c\", \"l\"]], str]] = None,\n    env_prefix: str = \"WASABI\",\n    fg_colors: Optional[Sequence] = None,\n    bg_colors: Optional[Sequence] = None,\n) -> str:\n    \"\"\"Format data as a table row.\n\n    data (Collection): The individual columns to format.\n    widths (Union[Sequence[int], int, Literal['auto']]): Column widths, either one integer for all\n        columns or an iterable of values. If \"auto\", widths will be calculated\n        automatically based on the largest value.\n    spacing (int): Spacing between columns, in spaces.\n    aligns (Optional[Union[Sequence[Literal['r', 'c', 'l']], str]]): Optional column\n        alignments in order. 'l' (left, default), 'r' (right) or 'c' (center).\n        If a string, value is used for all columns.\n    env_prefix (str): Prefix for environment variables, e.g.\n        WASABI_LOG_FRIENDLY.\n    fg_colors (Optional[Sequence]): Optional foreground colors for the columns, in order. None can be\n        specified for individual columns to retain the default foreground color.\n    bg_colors (Optional[Sequence]): Optional background colors for the columns, in order. None can be\n        specified for individual columns to retain the default background color.\n    RETURNS (str): The formatted row.\n    \"\"\"\n    env_log_friendly = os.getenv(\"{}_LOG_FRIENDLY\".format(env_prefix), False)\n    show_colors = (\n        supports_ansi()\n        and not env_log_friendly\n        and (fg_colors is not None or bg_colors is not None)\n    )\n    cols: List[str] = []\n    _aligns = (\n        [aligns for _ in data] if isinstance(aligns, str) else cast(List[str], aligns)\n    )\n    if not hasattr(widths, \"__iter__\") and widths != \"auto\":  # single number\n        widths = cast(List[int], [widths for _ in range(len(data))])\n    for i, col in enumerate(data):\n        align = ALIGN_MAP.get(_aligns[i] if _aligns and i < len(_aligns) else \"l\")\n        col_width = len(col) if widths == \"auto\" else cast(List[int], widths)[i]\n        tpl = \"{:%s%d}\" % (align, col_width)\n        col = tpl.format(str(col))\n        if show_colors:\n            fg = fg_colors[i] if fg_colors is not None else None\n            bg = bg_colors[i] if bg_colors is not None else None\n            col = _color(col, fg=fg, bg=bg)\n        cols.append(col)\n    return (\" \" * spacing).join(cols)\n\n\ndef _get_max_widths(data, header, footer, max_col):\n    all_data = list(data)\n    if header:\n        all_data.append(header)\n    if footer:\n        all_data.append(footer)\n    widths = [[len(str(col)) for col in item] for item in all_data]\n    return [min(max(w), max_col) for w in list(zip(*widths))]\n"
  },
  {
    "path": "wasabi/tests/__init__.py",
    "content": ""
  },
  {
    "path": "wasabi/tests/test-data/wasabi-test-notebook.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"eb5586f8\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import sys\\n\",\n    \"import wasabi\\n\",\n    \"\\n\",\n    \"wasabi.msg.warn(\\\"This is a test. This is only a test.\\\")\\n\",\n    \"if sys.version_info >= (3, 7):\\n\",\n    \"    assert wasabi.util.supports_ansi()\\n\",\n    \"\\n\",\n    \"print(sys.stdout)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.10.7\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "wasabi/tests/test_jupyter.py",
    "content": "from pathlib import Path\nimport subprocess\nimport os\nimport sys\n\nimport wasabi\n\nTEST_DATA = Path(__file__).absolute().parent / \"test-data\"\nWASABI_DIR = Path(wasabi.__file__).absolute().parent.parent\n\n\ndef test_jupyter():\n    # This runs some code in a jupyter notebook environment, but without actually\n    # starting up the notebook UI. Historically we once had a bug that caused crashes\n    # when importing wasabi in a jupyter notebook, because they replace\n    # sys.stdout/stderr with custom objects that aren't \"real\" files/ttys. So this makes\n    # sure that we can import and use wasabi inside a notebook without crashing.\n    env = dict(os.environ)\n    if \"PYTHONPATH\" in env:\n        env[\"PYTHONPATH\"] = f\"{WASABI_DIR}{os.pathsep}{env['PYTHONPATH']}\"\n    else:\n        env[\"PYTHONPATH\"] = str(WASABI_DIR)\n    subprocess.run(\n        [\n            sys.executable,\n            \"-m\",\n            \"nbconvert\",\n            str(TEST_DATA / \"wasabi-test-notebook.ipynb\"),\n            \"--execute\",\n            \"--stdout\",\n            \"--to\",\n            \"notebook\",\n        ],\n        env=env,\n        check=True,\n    )\n"
  },
  {
    "path": "wasabi/tests/test_markdown.py",
    "content": "import pytest\n\nfrom wasabi.markdown import MarkdownRenderer\n\n\ndef test_markdown():\n    md = MarkdownRenderer()\n    md.add(md.title(1, \"Title\"))\n    md.add(\"Paragraph with {}\".format(md.bold(\"bold\")))\n    md.add(md.list([\"foo\", \"bar\"]))\n    md.add(md.table([(\"a\", \"b\"), (\"c\", \"d\")], [\"foo\", \"bar\"]))\n    md.add(md.code_block('import spacy\\n\\nnlp = spacy.blank(\"en\")', \"python\"))\n    md.add(md.list([\"first\", \"second\"], numbered=True))\n    expected = \"\"\"# Title\\n\\nParagraph with **bold**\\n\\n- foo\\n- bar\\n\\n| foo | bar |\\n| --- | --- |\\n| a | b |\\n| c | d |\\n\\n```python\\nimport spacy\\n\\nnlp = spacy.blank(\"en\")\\n```\\n\\n1. first\\n2. second\"\"\"\n    assert md.text == expected\n\n\ndef test_markdown_table_aligns():\n    md = MarkdownRenderer()\n    md.add(md.table([(\"a\", \"b\", \"c\")], [\"foo\", \"bar\", \"baz\"], aligns=(\"c\", \"r\", \"l\")))\n    expected = \"\"\"| foo | bar | baz |\\n| :---: | ---: | --- |\\n| a | b | c |\"\"\"\n    assert md.text == expected\n    with pytest.raises(ValueError):\n        md.table([(\"a\", \"b\", \"c\")], [\"foo\", \"bar\", \"baz\"], aligns=(\"c\", \"r\"))\n    with pytest.raises(ValueError):\n        md.table([(\"a\", \"b\", \"c\")], [\"foo\", \"bar\", \"baz\"], aligns=(\"c\", \"r\", \"l\", \"l\"))\n"
  },
  {
    "path": "wasabi/tests/test_printer.py",
    "content": "import os\nimport re\nimport time\n\nimport pytest\n\nfrom wasabi.printer import Printer\nfrom wasabi.util import MESSAGES, NO_UTF8, supports_ansi\n\nSUPPORTS_ANSI = supports_ansi()\n\n\ndef test_printer():\n    p = Printer(no_print=True)\n    text = \"This is a test.\"\n    good = p.good(text)\n    fail = p.fail(text)\n    warn = p.warn(text)\n    info = p.info(text)\n    assert p.text(text) == text\n    if SUPPORTS_ANSI and not NO_UTF8:\n        assert good == \"\\x1b[38;5;2m\\u2714 {}\\x1b[0m\".format(text)\n        assert fail == \"\\x1b[38;5;1m\\u2718 {}\\x1b[0m\".format(text)\n        assert warn == \"\\x1b[38;5;3m\\u26a0 {}\\x1b[0m\".format(text)\n        assert info == \"\\x1b[38;5;4m\\u2139 {}\\x1b[0m\".format(text)\n    if SUPPORTS_ANSI and NO_UTF8:\n        assert good == \"\\x1b[38;5;2m[+] {}\\x1b[0m\".format(text)\n        assert fail == \"\\x1b[38;5;1m[x] {}\\x1b[0m\".format(text)\n        assert warn == \"\\x1b[38;5;3m[!] {}\\x1b[0m\".format(text)\n        assert info == \"\\x1b[38;5;4m[i] {}\\x1b[0m\".format(text)\n    if not SUPPORTS_ANSI and not NO_UTF8:\n        assert good == \"\\u2714 {}\".format(text)\n        assert fail == \"\\u2718 {}\".format(text)\n        assert warn == \"\\u26a0 {}\".format(text)\n        assert info == \"\\u2139 {}\".format(text)\n    if not SUPPORTS_ANSI and NO_UTF8:\n        assert good == \"[+] {}\".format(text)\n        assert fail == \"[x] {}\".format(text)\n        assert warn == \"[!] {}\".format(text)\n        assert info == \"[i] {}\".format(text)\n\n\ndef test_printer_print():\n    p = Printer()\n    text = \"This is a test.\"\n    p.good(text)\n    p.fail(text)\n    p.info(text)\n    p.text(text)\n\n\ndef test_printer_print_timestamp():\n    p = Printer(no_print=True, timestamp=True)\n    result = p.info(\"Hello world\")\n    matches = re.match(\"^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\", result)\n    assert matches\n\n\ndef test_printer_no_pretty():\n    p = Printer(no_print=True, pretty=False)\n    text = \"This is a test.\"\n    assert p.good(text) == text\n    assert p.fail(text) == text\n    assert p.warn(text) == text\n    assert p.info(text) == text\n    assert p.text(text) == text\n\n\ndef test_printer_custom():\n    colors = {\"yellow\": 220, \"purple\": 99}\n    icons = {\"warn\": \"\\u26a0\\ufe0f\", \"question\": \"?\"}\n    p = Printer(no_print=True, colors=colors, icons=icons)\n    text = \"This is a test.\"\n    purple_question = p.text(text, color=\"purple\", icon=\"question\")\n    warning = p.warn(text)\n    if SUPPORTS_ANSI and not NO_UTF8:\n        assert purple_question == \"\\x1b[38;5;99m? {}\\x1b[0m\".format(text)\n        assert warning == \"\\x1b[38;5;3m\\u26a0\\ufe0f {}\\x1b[0m\".format(text)\n    if SUPPORTS_ANSI and NO_UTF8:\n        assert purple_question == \"\\x1b[38;5;99m? {}\\x1b[0m\".format(text)\n        assert warning == \"\\x1b[38;5;3m?? {}\\x1b[0m\".format(text)\n    if not SUPPORTS_ANSI and not NO_UTF8:\n        assert purple_question == \"? {}\".format(text)\n        assert warning == \"\\u26a0\\ufe0f {}\".format(text)\n    if not SUPPORTS_ANSI and NO_UTF8:\n        assert purple_question == \"? {}\".format(text)\n        assert warning == \"?? {}\".format(text)\n\n\ndef test_color_as_int():\n    p = Printer(no_print=True)\n    text = \"This is a text.\"\n    result = p.text(text, color=220)\n    if SUPPORTS_ANSI:\n        assert result == \"\\x1b[38;5;220mThis is a text.\\x1b[0m\"\n    else:\n        assert result == \"This is a text.\"\n\n\ndef test_bg_color():\n    p = Printer(no_print=True)\n    text = \"This is a text.\"\n    result = p.text(text, bg_color=\"red\")\n    print(result)\n    if SUPPORTS_ANSI:\n        assert result == \"\\x1b[48;5;1mThis is a text.\\x1b[0m\"\n    else:\n        assert result == \"This is a text.\"\n\n\ndef test_bg_color_as_int():\n    p = Printer(no_print=True)\n    text = \"This is a text.\"\n    result = p.text(text, bg_color=220)\n    print(result)\n    if SUPPORTS_ANSI:\n        assert result == \"\\x1b[48;5;220mThis is a text.\\x1b[0m\"\n    else:\n        assert result == \"This is a text.\"\n\n\ndef test_color_and_bc_color():\n    p = Printer(no_print=True)\n    text = \"This is a text.\"\n    result = p.text(text, color=\"green\", bg_color=\"yellow\")\n    print(result)\n    if SUPPORTS_ANSI:\n        assert result == \"\\x1b[38;5;2;48;5;3mThis is a text.\\x1b[0m\"\n    else:\n        assert result == \"This is a text.\"\n\n\ndef test_printer_counts():\n    p = Printer()\n    text = \"This is a test.\"\n    for i in range(2):\n        p.good(text)\n    for i in range(1):\n        p.fail(text)\n    for i in range(4):\n        p.warn(text)\n    assert p.counts[MESSAGES.GOOD] == 2\n    assert p.counts[MESSAGES.FAIL] == 1\n    assert p.counts[MESSAGES.WARN] == 4\n\n\ndef test_printer_spaced():\n    p = Printer(no_print=True, pretty=False)\n    text = \"This is a test.\"\n    assert p.good(text) == text\n    assert p.good(text, spaced=True) == \"\\n{}\\n\".format(text)\n\n\ndef test_printer_divider():\n    p = Printer(line_max=20, no_print=True)\n    p.divider() == \"\\x1b[1m\\n================\\x1b[0m\"\n    p.divider(\"test\") == \"\\x1b[1m\\n====== test ======\\x1b[0m\"\n    p.divider(\"test\", char=\"*\") == \"\\x1b[1m\\n****** test ******\\x1b[0m\"\n    assert (\n        p.divider(\"This is a very long text, it is very long\")\n        == \"\\x1b[1m\\n This is a very long text, it is very long \\x1b[0m\"\n    )\n    with pytest.raises(ValueError):\n        p.divider(\"test\", char=\"~.\")\n\n\n@pytest.mark.parametrize(\"hide_animation\", [False, True])\ndef test_printer_loading(hide_animation):\n    p = Printer(hide_animation=hide_animation)\n    print(\"\\n\")\n    with p.loading(\"Loading...\"):\n        time.sleep(1)\n    p.good(\"Success!\")\n\n    with p.loading(\"Something else...\"):\n        time.sleep(2)\n    p.good(\"Yo!\")\n\n    with p.loading(\"Loading...\"):\n        time.sleep(1)\n    p.good(\"Success!\")\n\n\ndef test_printer_loading_raises_exception():\n    def loading_with_exception():\n        p = Printer()\n        print(\"\\n\")\n        with p.loading():\n            raise Exception(\"This is an error.\")\n\n    with pytest.raises(Exception):\n        loading_with_exception()\n\n\ndef test_printer_loading_no_print():\n    p = Printer(no_print=True)\n    with p.loading(\"Loading...\"):\n        time.sleep(1)\n    p.good(\"Success!\")\n\n\ndef test_printer_log_friendly():\n    text = \"This is a test.\"\n    ENV_LOG_FRIENDLY = \"WASABI_LOG_FRIENDLY\"\n    os.environ[ENV_LOG_FRIENDLY] = \"True\"\n    p = Printer(no_print=True)\n    assert p.good(text) in (\"\\u2714 This is a test.\", \"[+] This is a test.\")\n    del os.environ[ENV_LOG_FRIENDLY]\n\n\ndef test_printer_log_friendly_prefix():\n    text = \"This is a test.\"\n    ENV_LOG_FRIENDLY = \"CUSTOM_LOG_FRIENDLY\"\n    os.environ[ENV_LOG_FRIENDLY] = \"True\"\n    p = Printer(no_print=True, env_prefix=\"CUSTOM\")\n    assert p.good(text) in (\"\\u2714 This is a test.\", \"[+] This is a test.\")\n    print(p.good(text))\n    del os.environ[ENV_LOG_FRIENDLY]\n\n\n@pytest.mark.skip(reason=\"Now seems to raise TypeError: readonly attribute?\")\ndef test_printer_none_encoding(monkeypatch):\n    \"\"\"Test that printer works even if sys.stdout.encoding is set to None. This\n    previously caused a very confusing error.\"\"\"\n    monkeypatch.setattr(\"sys.stdout.encoding\", None)\n    p = Printer()  # noqa: F841\n\n\ndef test_printer_no_print_raise_on_exit():\n    \"\"\"Test that the printer raises if a non-zero exit code is provided, even\n    if no_print is set to True.\"\"\"\n    err = \"This is an error.\"\n    p = Printer(no_print=True, pretty=False)\n    with pytest.raises(SystemExit) as e:\n        p.fail(err, exits=True)\n    assert str(e.value).strip()[-len(err) :] == err\n"
  },
  {
    "path": "wasabi/tests/test_tables.py",
    "content": "import os\n\nimport pytest\n\nfrom wasabi.tables import row, table\nfrom wasabi.util import supports_ansi\n\nSUPPORTS_ANSI = supports_ansi()\n\n\n@pytest.fixture()\ndef data():\n    return [(\"Hello\", \"World\", \"12344342\"), (\"This is a test\", \"World\", \"1234\")]\n\n\n@pytest.fixture()\ndef header():\n    return [\"COL A\", \"COL B\", \"COL 3\"]\n\n\n@pytest.fixture()\ndef footer():\n    return [\"\", \"\", \"2030203.00\"]\n\n\n@pytest.fixture()\ndef fg_colors():\n    return [\"\", \"yellow\", \"87\"]\n\n\n@pytest.fixture()\ndef bg_colors():\n    return [\"green\", \"23\", \"\"]\n\n\ndef test_table_default(data):\n    result = table(data)\n    assert (\n        result\n        == \"\\nHello            World   12344342\\nThis is a test   World   1234    \\n\"\n    )\n\n\ndef test_table_header(data, header):\n    result = table(data, header=header)\n    assert (\n        result\n        == \"\\nCOL A            COL B   COL 3   \\nHello            World   12344342\\nThis is a test   World   1234    \\n\"\n    )\n\n\ndef test_table_header_footer_divider(data, header, footer):\n    result = table(data, header=header, footer=footer, divider=True)\n    assert (\n        result\n        == \"\\nCOL A            COL B   COL 3     \\n--------------   -----   ----------\\nHello            World   12344342  \\nThis is a test   World   1234      \\n--------------   -----   ----------\\n                         2030203.00\\n\"\n    )\n\n\ndef test_table_aligns(data):\n    result = table(data, aligns=(\"r\", \"c\", \"l\"))\n    assert (\n        result\n        == \"\\n         Hello   World   12344342\\nThis is a test   World   1234    \\n\"\n    )\n\n\ndef test_table_aligns_single(data):\n    result = table(data, aligns=\"r\")\n    assert (\n        result\n        == \"\\n         Hello   World   12344342\\nThis is a test   World       1234\\n\"\n    )\n\n\ndef test_table_widths():\n    data = [(\"a\", \"bb\", \"ccc\"), (\"d\", \"ee\", \"fff\")]\n    widths = (5, 2, 10)\n    result = table(data, widths=widths)\n    assert result == \"\\na       bb   ccc       \\nd       ee   fff       \\n\"\n\n\ndef test_row_single_widths():\n    data = (\"a\", \"bb\", \"ccc\")\n    result = row(data, widths=10)\n    assert result == \"a            bb           ccc       \"\n\n\ndef test_table_multiline(header):\n    data = [\n        (\"hello\", [\"foo\", \"bar\", \"baz\"], \"world\"),\n        (\"hello\", \"world\", [\"world 1\", \"world 2\"]),\n    ]\n    result = table(data, header=header, divider=True, multiline=True)\n    assert (\n        result\n        == \"\\nCOL A   COL B   COL 3  \\n-----   -----   -------\\nhello   foo     world  \\n        bar            \\n        baz            \\n                       \\nhello   world   world 1\\n                world 2\\n\"\n    )\n\n\ndef test_row_fg_colors(fg_colors):\n    result = row((\"Hello\", \"World\", \"12344342\"), fg_colors=fg_colors)\n    if SUPPORTS_ANSI:\n        assert (\n            result == \"Hello   \\x1b[38;5;3mWorld\\x1b[0m   \\x1b[38;5;87m12344342\\x1b[0m\"\n        )\n    else:\n        assert result == \"Hello   World   12344342\"\n\n\ndef test_row_bg_colors(bg_colors):\n    result = row((\"Hello\", \"World\", \"12344342\"), bg_colors=bg_colors)\n    if SUPPORTS_ANSI:\n        assert (\n            result == \"\\x1b[48;5;2mHello\\x1b[0m   \\x1b[48;5;23mWorld\\x1b[0m   12344342\"\n        )\n    else:\n        assert result == \"Hello   World   12344342\"\n\n\ndef test_row_fg_colors_and_bg_colors(fg_colors, bg_colors):\n    result = row(\n        (\"Hello\", \"World\", \"12344342\"), fg_colors=fg_colors, bg_colors=bg_colors\n    )\n    if SUPPORTS_ANSI:\n        assert (\n            result\n            == \"\\x1b[48;5;2mHello\\x1b[0m   \\x1b[38;5;3;48;5;23mWorld\\x1b[0m   \\x1b[38;5;87m12344342\\x1b[0m\"\n        )\n    else:\n        assert result == \"Hello   World   12344342\"\n\n\ndef test_row_fg_colors_and_bg_colors_log_friendly(fg_colors, bg_colors):\n    ENV_LOG_FRIENDLY = \"WASABI_LOG_FRIENDLY\"\n    os.environ[ENV_LOG_FRIENDLY] = \"True\"\n    result = row(\n        (\"Hello\", \"World\", \"12344342\"), fg_colors=fg_colors, bg_colors=bg_colors\n    )\n    assert result == \"Hello   World   12344342\"\n    del os.environ[ENV_LOG_FRIENDLY]\n\n\ndef test_row_fg_colors_and_bg_colors_log_friendly_prefix(fg_colors, bg_colors):\n    ENV_LOG_FRIENDLY = \"CUSTOM_LOG_FRIENDLY\"\n    os.environ[ENV_LOG_FRIENDLY] = \"True\"\n    result = row(\n        (\"Hello\", \"World\", \"12344342\"),\n        fg_colors=fg_colors,\n        bg_colors=bg_colors,\n        env_prefix=\"CUSTOM\",\n    )\n    assert result == \"Hello   World   12344342\"\n    del os.environ[ENV_LOG_FRIENDLY]\n\n\ndef test_row_fg_colors_and_bg_colors_supports_ansi_false(fg_colors, bg_colors):\n    os.environ[\"ANSI_COLORS_DISABLED\"] = \"True\"\n    result = row(\n        (\"Hello\", \"World\", \"12344342\"), fg_colors=fg_colors, bg_colors=bg_colors\n    )\n    assert result == \"Hello   World   12344342\"\n    del os.environ[\"ANSI_COLORS_DISABLED\"]\n\n\ndef test_colors_whole_table_with_automatic_widths(\n    data, header, footer, fg_colors, bg_colors\n):\n    result = table(\n        data,\n        header=header,\n        footer=footer,\n        divider=True,\n        fg_colors=fg_colors,\n        bg_colors=bg_colors,\n    )\n    if SUPPORTS_ANSI:\n        assert (\n            result\n            == \"\\n\\x1b[48;5;2mCOL A         \\x1b[0m   \\x1b[38;5;3;48;5;23mCOL B\\x1b[0m   \\x1b[38;5;87mCOL 3     \\x1b[0m\\n\\x1b[48;5;2m--------------\\x1b[0m   \\x1b[38;5;3;48;5;23m-----\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2mHello         \\x1b[0m   \\x1b[38;5;3;48;5;23mWorld\\x1b[0m   \\x1b[38;5;87m12344342  \\x1b[0m\\n\\x1b[48;5;2mThis is a test\\x1b[0m   \\x1b[38;5;3;48;5;23mWorld\\x1b[0m   \\x1b[38;5;87m1234      \\x1b[0m\\n\\x1b[48;5;2m--------------\\x1b[0m   \\x1b[38;5;3;48;5;23m-----\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2m              \\x1b[0m   \\x1b[38;5;3;48;5;23m     \\x1b[0m   \\x1b[38;5;87m2030203.00\\x1b[0m\\n\"\n        )\n    else:\n        assert (\n            result\n            == \"\\nCOL A            COL B   COL 3     \\n--------------   -----   ----------\\nHello            World   12344342  \\nThis is a test   World   1234      \\n--------------   -----   ----------\\n                         2030203.00\\n\"\n        )\n\n\ndef test_colors_whole_table_only_fg_colors(data, header, footer, fg_colors):\n    result = table(\n        data,\n        header=header,\n        footer=footer,\n        divider=True,\n        fg_colors=fg_colors,\n    )\n    if SUPPORTS_ANSI:\n        assert (\n            result\n            == \"\\nCOL A            \\x1b[38;5;3mCOL B\\x1b[0m   \\x1b[38;5;87mCOL 3     \\x1b[0m\\n--------------   \\x1b[38;5;3m-----\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\nHello            \\x1b[38;5;3mWorld\\x1b[0m   \\x1b[38;5;87m12344342  \\x1b[0m\\nThis is a test   \\x1b[38;5;3mWorld\\x1b[0m   \\x1b[38;5;87m1234      \\x1b[0m\\n--------------   \\x1b[38;5;3m-----\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\n                 \\x1b[38;5;3m     \\x1b[0m   \\x1b[38;5;87m2030203.00\\x1b[0m\\n\"\n        )\n    else:\n        assert (\n            result\n            == \"\\nCOL A            COL B   COL 3     \\n--------------   -----   ----------\\nHello            World   12344342  \\nThis is a test   World   1234      \\n--------------   -----   ----------\\n                         2030203.00\\n\"\n        )\n\n\ndef test_colors_whole_table_only_bg_colors(data, header, footer, bg_colors):\n    result = table(\n        data,\n        header=header,\n        footer=footer,\n        divider=True,\n        bg_colors=bg_colors,\n    )\n    if SUPPORTS_ANSI:\n        assert (\n            result\n            == \"\\n\\x1b[48;5;2mCOL A         \\x1b[0m   \\x1b[48;5;23mCOL B\\x1b[0m   COL 3     \\n\\x1b[48;5;2m--------------\\x1b[0m   \\x1b[48;5;23m-----\\x1b[0m   ----------\\n\\x1b[48;5;2mHello         \\x1b[0m   \\x1b[48;5;23mWorld\\x1b[0m   12344342  \\n\\x1b[48;5;2mThis is a test\\x1b[0m   \\x1b[48;5;23mWorld\\x1b[0m   1234      \\n\\x1b[48;5;2m--------------\\x1b[0m   \\x1b[48;5;23m-----\\x1b[0m   ----------\\n\\x1b[48;5;2m              \\x1b[0m   \\x1b[48;5;23m     \\x1b[0m   2030203.00\\n\"\n        )\n    else:\n        assert (\n            result\n            == \"\\nCOL A            COL B   COL 3     \\n--------------   -----   ----------\\nHello            World   12344342  \\nThis is a test   World   1234      \\n--------------   -----   ----------\\n                         2030203.00\\n\"\n        )\n\n\ndef test_colors_whole_table_with_supplied_spacing(\n    data, header, footer, fg_colors, bg_colors\n):\n    result = table(\n        data,\n        header=header,\n        footer=footer,\n        divider=True,\n        fg_colors=fg_colors,\n        bg_colors=bg_colors,\n        spacing=5,\n    )\n    if SUPPORTS_ANSI:\n        assert (\n            result\n            == \"\\n\\x1b[48;5;2mCOL A         \\x1b[0m     \\x1b[38;5;3;48;5;23mCOL B\\x1b[0m     \\x1b[38;5;87mCOL 3     \\x1b[0m\\n\\x1b[48;5;2m--------------\\x1b[0m     \\x1b[38;5;3;48;5;23m-----\\x1b[0m     \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2mHello         \\x1b[0m     \\x1b[38;5;3;48;5;23mWorld\\x1b[0m     \\x1b[38;5;87m12344342  \\x1b[0m\\n\\x1b[48;5;2mThis is a test\\x1b[0m     \\x1b[38;5;3;48;5;23mWorld\\x1b[0m     \\x1b[38;5;87m1234      \\x1b[0m\\n\\x1b[48;5;2m--------------\\x1b[0m     \\x1b[38;5;3;48;5;23m-----\\x1b[0m     \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2m              \\x1b[0m     \\x1b[38;5;3;48;5;23m     \\x1b[0m     \\x1b[38;5;87m2030203.00\\x1b[0m\\n\"\n        )\n    else:\n        assert (\n            result\n            == \"\\nCOL A              COL B     COL 3     \\n--------------     -----     ----------\\nHello              World     12344342  \\nThis is a test     World     1234      \\n--------------     -----     ----------\\n                             2030203.00\\n\"\n        )\n\n\ndef test_colors_whole_table_with_supplied_widths(\n    data, header, footer, fg_colors, bg_colors\n):\n    result = table(\n        data,\n        header=header,\n        footer=footer,\n        divider=True,\n        fg_colors=fg_colors,\n        bg_colors=bg_colors,\n        widths=(5, 2, 10),\n    )\n    if SUPPORTS_ANSI:\n        assert (\n            result\n            == \"\\n\\x1b[48;5;2mCOL A\\x1b[0m   \\x1b[38;5;3;48;5;23mCOL B\\x1b[0m   \\x1b[38;5;87mCOL 3     \\x1b[0m\\n\\x1b[48;5;2m-----\\x1b[0m   \\x1b[38;5;3;48;5;23m--\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2mHello\\x1b[0m   \\x1b[38;5;3;48;5;23mWorld\\x1b[0m   \\x1b[38;5;87m12344342  \\x1b[0m\\n\\x1b[48;5;2mThis is a test\\x1b[0m   \\x1b[38;5;3;48;5;23mWorld\\x1b[0m   \\x1b[38;5;87m1234      \\x1b[0m\\n\\x1b[48;5;2m-----\\x1b[0m   \\x1b[38;5;3;48;5;23m--\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2m     \\x1b[0m   \\x1b[38;5;3;48;5;23m  \\x1b[0m   \\x1b[38;5;87m2030203.00\\x1b[0m\\n\"\n        )\n    else:\n        assert (\n            result\n            == \"\\nCOL A   COL B   COL 3     \\n-----   --   ----------\\nHello   World   12344342  \\nThis is a test   World   1234      \\n-----   --   ----------\\n             2030203.00\\n\"\n        )\n\n\ndef test_colors_whole_table_with_single_alignment(\n    data, header, footer, fg_colors, bg_colors\n):\n    result = table(\n        data,\n        header=header,\n        footer=footer,\n        divider=True,\n        fg_colors=fg_colors,\n        bg_colors=bg_colors,\n        aligns=\"r\",\n    )\n    if SUPPORTS_ANSI:\n        assert (\n            result\n            == \"\\n\\x1b[48;5;2m         COL A\\x1b[0m   \\x1b[38;5;3;48;5;23mCOL B\\x1b[0m   \\x1b[38;5;87m     COL 3\\x1b[0m\\n\\x1b[48;5;2m--------------\\x1b[0m   \\x1b[38;5;3;48;5;23m-----\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2m         Hello\\x1b[0m   \\x1b[38;5;3;48;5;23mWorld\\x1b[0m   \\x1b[38;5;87m  12344342\\x1b[0m\\n\\x1b[48;5;2mThis is a test\\x1b[0m   \\x1b[38;5;3;48;5;23mWorld\\x1b[0m   \\x1b[38;5;87m      1234\\x1b[0m\\n\\x1b[48;5;2m--------------\\x1b[0m   \\x1b[38;5;3;48;5;23m-----\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2m              \\x1b[0m   \\x1b[38;5;3;48;5;23m     \\x1b[0m   \\x1b[38;5;87m2030203.00\\x1b[0m\\n\"\n        )\n    else:\n        assert (\n            result\n            == \"\\n         COL A   COL B        COL 3\\n--------------   -----   ----------\\n         Hello   World     12344342\\nThis is a test   World         1234\\n--------------   -----   ----------\\n                         2030203.00\\n\"\n        )\n\n\ndef test_colors_whole_table_with_multiple_alignment(\n    data, header, footer, fg_colors, bg_colors\n):\n    result = table(\n        data,\n        header=header,\n        footer=footer,\n        divider=True,\n        fg_colors=fg_colors,\n        bg_colors=bg_colors,\n        aligns=(\"c\", \"r\", \"l\"),\n    )\n    if SUPPORTS_ANSI:\n        assert (\n            result\n            == \"\\n\\x1b[48;5;2m    COL A     \\x1b[0m   \\x1b[38;5;3;48;5;23mCOL B\\x1b[0m   \\x1b[38;5;87mCOL 3     \\x1b[0m\\n\\x1b[48;5;2m--------------\\x1b[0m   \\x1b[38;5;3;48;5;23m-----\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2m    Hello     \\x1b[0m   \\x1b[38;5;3;48;5;23mWorld\\x1b[0m   \\x1b[38;5;87m12344342  \\x1b[0m\\n\\x1b[48;5;2mThis is a test\\x1b[0m   \\x1b[38;5;3;48;5;23mWorld\\x1b[0m   \\x1b[38;5;87m1234      \\x1b[0m\\n\\x1b[48;5;2m--------------\\x1b[0m   \\x1b[38;5;3;48;5;23m-----\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2m              \\x1b[0m   \\x1b[38;5;3;48;5;23m     \\x1b[0m   \\x1b[38;5;87m2030203.00\\x1b[0m\\n\"\n        )\n    else:\n        assert (\n            result\n            == \"\\n    COL A        COL B   COL 3     \\n--------------   -----   ----------\\n    Hello        World   12344342  \\nThis is a test   World   1234      \\n--------------   -----   ----------\\n                         2030203.00\\n\"\n        )\n\n\ndef test_colors_whole_table_with_multiline(data, header, footer, fg_colors, bg_colors):\n    result = table(\n        data=(([\"Charles\", \"Quinton\", \"Murphy\"], \"my\", \"brother\"), (\"1\", \"2\", \"3\")),\n        fg_colors=fg_colors,\n        bg_colors=bg_colors,\n        multiline=True,\n    )\n    if SUPPORTS_ANSI:\n        assert (\n            result\n            == \"\\n\\x1b[48;5;2mCharles\\x1b[0m   \\x1b[38;5;3;48;5;23mmy\\x1b[0m   \\x1b[38;5;87mbrother\\x1b[0m\\n\\x1b[48;5;2mQuinton\\x1b[0m   \\x1b[38;5;3;48;5;23m  \\x1b[0m   \\x1b[38;5;87m       \\x1b[0m\\n\\x1b[48;5;2mMurphy \\x1b[0m   \\x1b[38;5;3;48;5;23m  \\x1b[0m   \\x1b[38;5;87m       \\x1b[0m\\n\\x1b[48;5;2m       \\x1b[0m   \\x1b[38;5;3;48;5;23m  \\x1b[0m   \\x1b[38;5;87m       \\x1b[0m\\n\\x1b[48;5;2m1      \\x1b[0m   \\x1b[38;5;3;48;5;23m2 \\x1b[0m   \\x1b[38;5;87m3      \\x1b[0m\\n\"\n        )\n    else:\n        assert (\n            result\n            == \"\\nCharles   my   brother\\nQuinton               \\nMurphy                \\n                      \\n1         2    3      \\n\"\n        )\n\n\ndef test_colors_whole_table_log_friendly(data, header, footer, fg_colors, bg_colors):\n    ENV_LOG_FRIENDLY = \"WASABI_LOG_FRIENDLY\"\n    os.environ[ENV_LOG_FRIENDLY] = \"True\"\n    result = table(\n        data,\n        header=header,\n        footer=footer,\n        divider=True,\n        fg_colors=fg_colors,\n        bg_colors=bg_colors,\n    )\n    assert (\n        result\n        == \"\\nCOL A            COL B   COL 3     \\n--------------   -----   ----------\\nHello            World   12344342  \\nThis is a test   World   1234      \\n--------------   -----   ----------\\n                         2030203.00\\n\"\n    )\n    del os.environ[ENV_LOG_FRIENDLY]\n\n\ndef test_colors_whole_table_log_friendly_prefix(\n    data, header, footer, fg_colors, bg_colors\n):\n    ENV_LOG_FRIENDLY = \"CUSTOM_LOG_FRIENDLY\"\n    os.environ[ENV_LOG_FRIENDLY] = \"True\"\n    result = table(\n        data,\n        header=header,\n        footer=footer,\n        divider=True,\n        fg_colors=fg_colors,\n        bg_colors=bg_colors,\n        env_prefix=\"CUSTOM\",\n    )\n    assert (\n        result\n        == \"\\nCOL A            COL B   COL 3     \\n--------------   -----   ----------\\nHello            World   12344342  \\nThis is a test   World   1234      \\n--------------   -----   ----------\\n                         2030203.00\\n\"\n    )\n    del os.environ[ENV_LOG_FRIENDLY]\n\n\ndef test_colors_whole_table_supports_ansi_false(\n    data, header, footer, fg_colors, bg_colors\n):\n    os.environ[\"ANSI_COLORS_DISABLED\"] = \"True\"\n    result = table(\n        data,\n        header=header,\n        footer=footer,\n        divider=True,\n        fg_colors=fg_colors,\n        bg_colors=bg_colors,\n    )\n    assert (\n        result\n        == \"\\nCOL A            COL B   COL 3     \\n--------------   -----   ----------\\nHello            World   12344342  \\nThis is a test   World   1234      \\n--------------   -----   ----------\\n                         2030203.00\\n\"\n    )\n    del os.environ[\"ANSI_COLORS_DISABLED\"]\n\n\ndef test_colors_whole_table_color_values(data, header, footer, fg_colors, bg_colors):\n    result = table(\n        data,\n        header=header,\n        footer=footer,\n        divider=True,\n        fg_colors=fg_colors,\n        bg_colors=bg_colors,\n        color_values={\"yellow\": 11},\n    )\n    if SUPPORTS_ANSI:\n        assert (\n            result\n            == \"\\n\\x1b[48;5;2mCOL A         \\x1b[0m   \\x1b[38;5;11;48;5;23mCOL B\\x1b[0m   \\x1b[38;5;87mCOL 3     \\x1b[0m\\n\\x1b[48;5;2m--------------\\x1b[0m   \\x1b[38;5;11;48;5;23m-----\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2mHello         \\x1b[0m   \\x1b[38;5;11;48;5;23mWorld\\x1b[0m   \\x1b[38;5;87m12344342  \\x1b[0m\\n\\x1b[48;5;2mThis is a test\\x1b[0m   \\x1b[38;5;11;48;5;23mWorld\\x1b[0m   \\x1b[38;5;87m1234      \\x1b[0m\\n\\x1b[48;5;2m--------------\\x1b[0m   \\x1b[38;5;11;48;5;23m-----\\x1b[0m   \\x1b[38;5;87m----------\\x1b[0m\\n\\x1b[48;5;2m              \\x1b[0m   \\x1b[38;5;11;48;5;23m     \\x1b[0m   \\x1b[38;5;87m2030203.00\\x1b[0m\\n\"\n        )\n    else:\n        assert (\n            result\n            == \"\\nCOL A            COL B   COL 3     \\n--------------   -----   ----------\\nHello            World   12344342  \\nThis is a test   World   1234      \\n--------------   -----   ----------\\n                         2030203.00\\n\"\n        )\n"
  },
  {
    "path": "wasabi/tests/test_traceback.py",
    "content": "import traceback\n\nimport pytest\n\nfrom wasabi.traceback_printer import TracebackPrinter\n\n\n@pytest.fixture\ndef tb():\n    return traceback.extract_stack()\n\n\ndef test_traceback_printer(tb):\n    tbp = TracebackPrinter(tb_base=\"wasabi\")\n    msg = tbp(\"Hello world\", \"This is a test\", tb=tb)\n    print(msg)\n\n\ndef test_traceback_printer_highlight(tb):\n    tbp = TracebackPrinter(tb_base=\"wasabi\")\n    msg = tbp(\"Hello world\", \"This is a test\", tb=tb, highlight=\"kwargs\")\n    print(msg)\n\n\ndef test_traceback_printer_custom_colors(tb):\n    tbp = TracebackPrinter(\n        tb_base=\"wasabi\", color_error=\"blue\", color_highlight=\"green\", color_tb=\"yellow\"\n    )\n    msg = tbp(\"Hello world\", \"This is a test\", tb=tb, highlight=\"kwargs\")\n    print(msg)\n\n\ndef test_traceback_printer_only_title(tb):\n    tbp = TracebackPrinter(tb_base=\"wasabi\")\n    msg = tbp(\"Hello world\", tb=tb)\n    print(msg)\n\n\ndef test_traceback_dot_relative_path_tb_base(tb):\n    tbp = TracebackPrinter(tb_base=\".\")\n    msg = tbp(\"Hello world\", tb=tb)\n    print(msg)\n\n\ndef test_traceback_tb_base_none(tb):\n    tbp = TracebackPrinter()\n    msg = tbp(\"Hello world\", tb=tb)\n    print(msg)\n\n\ndef test_traceback_printer_no_tb():\n    tbp = TracebackPrinter(tb_base=\"wasabi\")\n    msg = tbp(\"Hello world\", \"This is a test\")\n    print(msg)\n\n\ndef test_traceback_printer_custom_tb_range():\n    tbp = TracebackPrinter(tb_range_start=-10, tb_range_end=-3)\n    msg = tbp(\"Hello world\", \"This is a test\")\n    print(msg)\n\n\ndef test_traceback_printer_custom_tb_range_start():\n    tbp = TracebackPrinter(tb_range_start=-1)\n    msg = tbp(\"Hello world\", \"This is a test\")\n    print(msg)\n"
  },
  {
    "path": "wasabi/tests/test_util.py",
    "content": "import pytest\n\nfrom wasabi.util import color, diff_strings, format_repr, locale_escape, wrap\n\n\ndef test_color():\n    assert color(\"test\", fg=\"green\") == \"\\x1b[38;5;2mtest\\x1b[0m\"\n    assert color(\"test\", fg=4) == \"\\x1b[38;5;4mtest\\x1b[0m\"\n    assert color(\"test\", bold=True) == \"\\x1b[1mtest\\x1b[0m\"\n    assert color(\"test\", fg=\"red\", underline=True) == \"\\x1b[4;38;5;1mtest\\x1b[0m\"\n    assert (\n        color(\"test\", fg=7, bg=\"red\", bold=True) == \"\\x1b[1;38;5;7;48;5;1mtest\\x1b[0m\"\n    )\n\n\ndef test_wrap():\n    text = \"Hello world, this is a test.\"\n    assert wrap(text, indent=0) == text\n    assert wrap(text, indent=4) == \"    Hello world, this is a test.\"\n    assert wrap(text, wrap_max=10, indent=0) == \"Hello\\nworld,\\nthis is a\\ntest.\"\n    assert (\n        wrap(text, wrap_max=5, indent=2)\n        == \"  Hello\\n  world,\\n  this\\n  is\\n  a\\n  test.\"\n    )\n\n\ndef test_format_repr():\n    obj = {\"hello\": \"world\", \"test\": 123}\n    formatted = format_repr(obj)\n    assert formatted.replace(\"u'\", \"'\") in [\n        \"{'hello': 'world', 'test': 123}\",\n        \"{'test': 123, 'hello': 'world'}\",\n    ]\n    formatted = format_repr(obj, max_len=10)\n    assert formatted.replace(\"u'\", \"'\") in [\n        \"{'hel ...  123}\",\n        \"{'tes ... rld'}\",\n        \"{'te ... rld'}\",\n    ]\n    formatted = format_repr(obj, max_len=10, ellipsis=\"[...]\")\n    assert formatted.replace(\"u'\", \"'\") in [\n        \"{'hel [...]  123}\",\n        \"{'tes [...] rld'}\",\n        \"{'te [...] rld'}\",\n    ]\n\n\n@pytest.mark.parametrize(\n    \"text,non_ascii\",\n    [\n        (\"abc\", [\"abc\"]),\n        (\"\\u2714 abc\", [\"? abc\"]),\n        (\"👻\", [\"??\", \"?\"]),  # On Python 3 windows, this becomes \"?\" instead of \"??\"\n    ],\n)\ndef test_locale_escape(text, non_ascii):\n    result = locale_escape(text)\n    assert result == text or result in non_ascii\n    print(result)\n\n\ndef test_diff_strings():\n    a = \"hello\\nworld\\nwide\\nweb\"\n    b = \"yo\\nwide\\nworld\\nweb\"\n    expected = \"\\x1b[38;5;16;48;5;2myo\\x1b[0m\\n\\x1b[38;5;16;48;5;2mwide\\x1b[0m\\n\\x1b[38;5;16;48;5;1mhello\\x1b[0m\\nworld\\n\\x1b[38;5;16;48;5;1mwide\\x1b[0m\\nweb\"\n    assert diff_strings(a, b) == expected\n\n\ndef test_diff_strings_with_symbols():\n    a = \"hello\\nworld\\nwide\\nweb\"\n    b = \"yo\\nwide\\nworld\\nweb\"\n    expected = \"\\x1b[38;5;16;48;5;2m+ yo\\x1b[0m\\n\\x1b[38;5;16;48;5;2m+ wide\\x1b[0m\\n\\x1b[38;5;16;48;5;1m- hello\\x1b[0m\\nworld\\n\\x1b[38;5;16;48;5;1m- wide\\x1b[0m\\nweb\"\n    assert diff_strings(a, b, add_symbols=True) == expected\n"
  },
  {
    "path": "wasabi/traceback_printer.py",
    "content": "import os\nfrom typing import Optional, Tuple, Union\n\nfrom .util import NO_UTF8, color, supports_ansi\n\nLINE_EDGE = \"└─\" if not NO_UTF8 else \"|_\"\nLINE_FORK = \"├─\" if not NO_UTF8 else \"|__\"\nLINE_PATH = \"──\" if not NO_UTF8 else \"__\"\n\n\nclass TracebackPrinter(object):\n    def __init__(\n        self,\n        color_error: Union[str, int] = \"red\",\n        color_tb: Union[str, int] = \"blue\",\n        color_highlight: Union[str, int] = \"yellow\",\n        indent: int = 2,\n        tb_base: Optional[str] = None,\n        tb_exclude: Tuple = tuple(),\n        tb_range_start: int = -5,\n        tb_range_end: int = -2,\n    ):\n        \"\"\"Initialize a traceback printer.\n\n        color_error (Union[str, int]): Color name or code for errors.\n        color_tb (Union[str, int]): Color name or code for traceback headline.\n        color_highlight (Union[str, int]): Color name or code for highlights.\n        indent (int): Indentation in spaces.\n        tb_base (Optional[str]): Optional name of directory to use to show relative paths. For\n            example, \"thinc\" will look for the last occurence of \"/thinc/\" in\n            a path and only show path to the right of it.\n        tb_exclude (tuple): List of filenames to exclude from traceback.\n        tb_range_start (int): The starting index from a traceback to include.\n        tb_range_end (int): The final index from a traceback to include. If None\n            the traceback will continue until the last record.\n        RETURNS (TracebackPrinter): The traceback printer.\n        \"\"\"\n        self.color_error = color_error\n        self.color_tb = color_tb\n        self.color_highlight = color_highlight\n        self.indent = \" \" * indent\n        if tb_base == \".\":\n            tb_base = \"{}{}\".format(os.getcwd(), os.path.sep)\n        elif tb_base is not None:\n            tb_base = \"/{}/\".format(tb_base)\n        self.tb_base = tb_base\n        self.tb_exclude = tuple(tb_exclude)\n        self.tb_range_start = tb_range_start\n        self.tb_range_end = tb_range_end\n        self.supports_ansi = supports_ansi()\n\n    def __call__(self, title: str, *texts, **settings) -> str:\n        \"\"\"Output custom formatted tracebacks and errors.\n\n        title (str): The message title.\n        *texts (str): The texts to print (one per line).\n        RETURNS (str): The formatted traceback. Can be printed or raised\n            by custom exception.\n        \"\"\"\n        highlight = settings.get(\"highlight\", False)\n        tb = settings.get(\"tb\", None)\n        if self.supports_ansi:  # use first line as title\n            title = color(title, fg=self.color_error, bold=True)\n        info = \"\\n\" + \"\\n\".join([self.indent + text for text in texts]) if texts else \"\"\n        tb = self._get_traceback(tb, highlight) if tb else \"\"\n        msg = \"\\n\\n{}{}{}{}\\n\".format(self.indent, title, info, tb)\n        return msg\n\n    def _get_traceback(self, tb, highlight):\n        # Exclude certain file names from traceback\n        tb = [record for record in tb if not record[0].endswith(self.tb_exclude)]\n        tb_range = (\n            tb[self.tb_range_start : self.tb_range_end]\n            if self.tb_range_end is not None\n            else tb[self.tb_range_start :]\n        )\n        tb_list = [\n            self._format_traceback(path, line, fn, text, i, len(tb_range), highlight)\n            for i, (path, line, fn, text) in enumerate(tb_range)\n        ]\n        tb_data = \"\\n\".join(tb_list).strip()\n        title = \"Traceback:\"\n        if self.supports_ansi:\n            title = color(title, fg=self.color_tb, bold=True)\n        return \"\\n\\n{indent}{title}\\n{indent}{tb}\".format(\n            title=title, tb=tb_data, indent=self.indent\n        )\n\n    def _format_traceback(self, path, line, fn, text, i, count, highlight):\n        template = \"{base_indent}{indent} {fn} in {path}:{line}{text}\"\n        indent = (LINE_EDGE if i == count - 1 else LINE_FORK) + LINE_PATH * i\n        if self.tb_base and self.tb_base in path:\n            path = path.rsplit(self.tb_base, 1)[1]\n        text = self._format_user_error(text, i, highlight) if i == count - 1 else \"\"\n        if self.supports_ansi:\n            fn = color(fn, bold=True)\n            path = color(path, underline=True)\n        return template.format(\n            base_indent=self.indent,\n            line=line,\n            indent=indent,\n            text=text,\n            fn=fn,\n            path=path,\n        )\n\n    def _format_user_error(self, text, i, highlight):\n        spacing = \"  \" * i + \" >>>\"\n        if self.supports_ansi:\n            spacing = color(spacing, fg=self.color_error)\n        if highlight and self.supports_ansi:\n            formatted_highlight = color(highlight, fg=self.color_highlight)\n            text = text.replace(highlight, formatted_highlight)\n        return \"\\n{}  {} {}\".format(self.indent, spacing, text)\n"
  },
  {
    "path": "wasabi/util.py",
    "content": "import difflib\nimport os\nimport sys\nimport textwrap\nfrom typing import Any, Optional, Tuple, Union\n\nSTDOUT_ENCODING = sys.stdout.encoding if hasattr(sys.stdout, \"encoding\") else None\nENCODING = STDOUT_ENCODING or \"ascii\"\nNO_UTF8 = ENCODING.lower() not in (\"utf8\", \"utf-8\")\n\n\n# Environment variables\nENV_ANSI_DISABLED = \"ANSI_COLORS_DISABLED\"  # no colors\n\n\nclass MESSAGES(object):\n    GOOD = \"good\"\n    FAIL = \"fail\"\n    WARN = \"warn\"\n    INFO = \"info\"\n\n\nCOLORS = {\n    MESSAGES.GOOD: 2,\n    MESSAGES.FAIL: 1,\n    MESSAGES.WARN: 3,\n    MESSAGES.INFO: 4,\n    \"red\": 1,\n    \"green\": 2,\n    \"yellow\": 3,\n    \"blue\": 4,\n    \"pink\": 5,\n    \"cyan\": 6,\n    \"white\": 7,\n    \"grey\": 8,\n    \"black\": 16,\n}\n\n\nICONS = {\n    MESSAGES.GOOD: \"\\u2714\" if not NO_UTF8 else \"[+]\",\n    MESSAGES.FAIL: \"\\u2718\" if not NO_UTF8 else \"[x]\",\n    MESSAGES.WARN: \"\\u26a0\" if not NO_UTF8 else \"[!]\",\n    MESSAGES.INFO: \"\\u2139\" if not NO_UTF8 else \"[i]\",\n}\n\nINSERT_SYMBOL = \"+\"\nDELETE_SYMBOL = \"-\"\n\n\ndef color(\n    text: str,\n    fg: Optional[Union[str, int]] = None,\n    bg: Optional[Union[str, int]] = None,\n    bold: bool = False,\n    underline: bool = False,\n) -> str:\n    \"\"\"Color text by applying ANSI escape sequence.\n\n    text (str): The text to be formatted.\n    fg (Optional[Union[str, int]]): Optional foreground color. String name or 0 - 256 (see COLORS).\n    bg (Optional[Union[str, int]]): Optional background color. String name or 0 - 256 (see COLORS).\n    bold (bool): Format text in bold.\n    underline (bool): Underline text.\n    RETURNS (str): The formatted text.\n    \"\"\"\n    fg = COLORS.get(fg, fg)  # type: ignore\n    bg = COLORS.get(bg, bg)  # type: ignore\n    if not any([fg, bg, bold]):\n        return text\n    styles = []\n    if bold:\n        styles.append(\"1\")\n    if underline:\n        styles.append(\"4\")\n    if fg:\n        styles.append(\"38;5;{}\".format(fg))\n    if bg:\n        styles.append(\"48;5;{}\".format(bg))\n    return \"\\x1b[{}m{}\\x1b[0m\".format(\";\".join(styles), text)\n\n\ndef wrap(text: Any, wrap_max: int = 80, indent: int = 4) -> str:\n    \"\"\"Wrap text at given width using textwrap module.\n\n    text (Any): The text to wrap.\n    wrap_max (int): Maximum line width, including indentation. Defaults to 80.\n    indent (int): Number of spaces used for indentation. Defaults to 4.\n    RETURNS (str): The wrapped text with line breaks.\n    \"\"\"\n    indent_str = indent * \" \"\n    wrap_width = wrap_max - len(indent_str)\n    text = str(text)\n    return textwrap.fill(\n        text,\n        width=wrap_width,\n        initial_indent=indent_str,\n        subsequent_indent=indent_str,\n        break_long_words=False,\n        break_on_hyphens=False,\n    )\n\n\ndef format_repr(obj: Any, max_len: int = 50, ellipsis: str = \"...\") -> str:\n    \"\"\"Wrapper around `repr()` to print shortened and formatted string version.\n\n    obj: The object to represent.\n    max_len (int): Maximum string length. Longer strings will be cut in the\n        middle so only the beginning and end is displayed, separated by ellipsis.\n    ellipsis (str): Ellipsis character(s), e.g. \"...\".\n    RETURNS (str): The formatted representation.\n    \"\"\"\n    string = repr(obj)\n    if len(string) >= max_len:\n        half = int(max_len / 2)\n        return \"{} {} {}\".format(string[:half], ellipsis, string[-half:])\n    else:\n        return string\n\n\ndef diff_strings(\n    a: str,\n    b: str,\n    fg: Union[str, int] = \"black\",\n    bg: Union[Tuple[str, str], Tuple[int, int]] = (\"green\", \"red\"),\n    add_symbols: bool = False,\n) -> str:\n    \"\"\"Compare two strings and return a colored diff with red/green background\n    for deletion and insertions.\n\n    a (str): The first string to diff.\n    b (str): The second string to diff.\n    fg (Union[str, int]): Foreground color. String name or 0 - 256 (see COLORS).\n    bg (Union[Tuple[str, str], Tuple[int, int]]): Background colors as\n        (insert, delete) tuple of string name or 0 - 256 (see COLORS).\n    add_symbols (bool): Whether to add symbols before the diff lines. Uses '+'\n        for inserts and '-' for deletions. Default is False.\n    RETURNS (str): The formatted diff.\n    \"\"\"\n    a_list = a.split(\"\\n\")\n    b_list = b.split(\"\\n\")\n    output = []\n    matcher = difflib.SequenceMatcher(None, a_list, b_list)\n    for opcode, a0, a1, b0, b1 in matcher.get_opcodes():\n        if opcode == \"equal\":\n            for item in a_list[a0:a1]:\n                output.append(item)\n        if opcode == \"insert\" or opcode == \"replace\":\n            for item in b_list[b0:b1]:\n                item = \"{} {}\".format(INSERT_SYMBOL, item) if add_symbols else item\n                output.append(color(item, fg=fg, bg=bg[0]))\n        if opcode == \"delete\" or opcode == \"replace\":\n            for item in a_list[a0:a1]:\n                item = \"{} {}\".format(DELETE_SYMBOL, item) if add_symbols else item\n                output.append(color(item, fg=fg, bg=bg[1]))\n    return \"\\n\".join(output)\n\n\ndef get_raw_input(\n    description: str, default: Optional[Union[str, bool]] = False, indent: int = 4\n) -> str:\n    \"\"\"Get user input from the command line via raw_input / input.\n\n    description (str): Text to display before prompt.\n    default (Optional[Union[str, bool]]): Optional default value to display with prompt.\n    indent (int): Indentation in spaces.\n    RETURNS (str): User input.\n    \"\"\"\n    additional = \" (default: {})\".format(default) if default else \"\"\n    prompt = wrap(\"{}{}: \".format(description, additional), indent=indent)\n    user_input = input(prompt)\n    return user_input\n\n\ndef locale_escape(string: Any, errors: str = \"replace\") -> str:\n    \"\"\"Mangle non-supported characters, for savages with ASCII terminals.\n\n    string (Any): The string to escape.\n    errors (str): The str.encode errors setting. Defaults to `\"replace\"`.\n    RETURNS (str): The escaped string.\n    \"\"\"\n    string = str(string)\n    string = string.encode(ENCODING, errors).decode(\"utf8\")\n    return string\n\n\ndef can_render(string: str) -> bool:\n    \"\"\"Check if terminal can render unicode characters, e.g. special loading\n    icons. Can be used to display fallbacks for ASCII terminals.\n\n    string (str): The string to render.\n    RETURNS (bool): Whether the terminal can render the text.\n    \"\"\"\n    try:\n        string.encode(ENCODING)\n        return True\n    except UnicodeEncodeError:\n        return False\n\n\ndef supports_ansi() -> bool:\n    \"\"\"Returns True if the running system's terminal supports ANSI escape\n    sequences for color, formatting etc. and False otherwise.\n\n    RETURNS (bool): Whether the terminal supports ANSI colors.\n    \"\"\"\n    if os.getenv(ENV_ANSI_DISABLED):\n        return False\n    # We require colorama on Windows Python 3.7+, but we might be running on Unix, or we\n    # might be running on Windows Python 3.6. In both cases, colorama might be missing,\n    # *or* there might by accident happen to be an install of an old version that\n    # doesn't have just_fix_windows_console. So we need to confirm not just that we can\n    # import colorama, but that we can import just_fix_windows_console.\n    try:\n        from colorama import just_fix_windows_console\n    except ImportError:\n        if sys.platform == \"win32\" and \"ANSICON\" not in os.environ:\n            return False\n    else:\n        just_fix_windows_console()\n    return True\n"
  }
]