diff --git a/.coveragerc b/.coveragerc index ff1a046c08..2afac8dc60 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,7 +2,8 @@ omit = rich/jupyter.py rich/_windows.py rich/_timer.py - + rich/diagnose.py + [report] exclude_lines = pragma: no cover diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b19a15c10e..6d6f45677f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,27 +6,35 @@ labels: Needs triage assignees: "" --- -**Read the docs** -You might find a solution to your problem in the [docs](https://rich.readthedocs.io/en/latest/introduction.html) -- consider using the search function there. +You may find a solution to your problem in the [docs](https://rich.readthedocs.io/en/latest/introduction.html) or [issues](https://github.com/willmcgugan/rich/issues). **Describe the bug** -A clear and concise description of what the bug is. -**To Reproduce** -A minimal code example that reproduces the problem would be a big help if you can provide it. If the issue is visual in nature, consider posting a screenshot. +Edit this with a clear and concise description of what the bug. + +Provide a minimal code example that demonstrates the issue if you can. If the issue is visual in nature, consider posting a screenshot. **Platform** +
+Click to expand + What platform (Win/Linux/Mac) are you running on? What terminal software are you using? -**Diagnose** -I may ask you to cut and paste the output of the following commands. It may save some time if you do it now. +I may ask you to copy and paste the output of the following commands. It may save some time if you do it now. + +If you're using Rich in a terminal: ``` python -m rich.diagnose -python -m rich._windows pip freeze | grep rich ``` -**Did I help?** +If you're using Rich in a Jupyter Notebook, run the following snippet in a cell +and paste the output in your bug report. + +```python +from rich.diagnose import report +report() +``` -If I was able to resolve your problem, consider [sponsoring](https://github.com/sponsors/willmcgugan) my work on Rich, or [buy me a coffee](https://ko-fi.com/willmcgugan) to say thanks. +
diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 95daf9d31b..28db4fc2c9 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -7,4 +7,4 @@ jobs: - uses: actions/checkout@v2 - run: python3 -m pip install codespell - run: codespell --ignore-words-list="ba,fo,hel,revered,womens" - --skip="./README.de.md,./README.es.md,./README.sv.md,./README.fr.md,./README.de-ch.md,./README.hi.md,./README.pt-br.md,*.svg" + --skip="./README.de.md,./README.es.md,./README.sv.md,./README.fr.md,./README.de-ch.md,./README.hi.md,./README.pt-br.md,./README.it.md,*.svg" diff --git a/.github/workflows/comment.yml b/.github/workflows/comment.yml index 5c2bd86b79..8da8617c52 100644 --- a/.github/workflows/comment.yml +++ b/.github/workflows/comment.yml @@ -15,11 +15,4 @@ jobs: body: | Did I solve your problem? - Consider [sponsoring](https://github.com/sponsors/willmcgugan) the ongoing work on Rich and Textual. - - Or buy me a [coffee](https://ko-fi.com/willmcgugan) to say thanks. - - – [Will McGugan](https://twitter.com/willmcgugan) - - - + Why not buy the devs a [coffee](https://ko-fi.com/textualize) to say thanks? diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..bbae40c844 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: check-ast + - id: check-builtin-literals + - id: check-case-conflict + - id: check-docstring-first + - id: check-merge-conflict + - id: check-json + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace + - id: check-vcs-permalinks + - id: check-shebang-scripts-are-executable + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.9.0 + hooks: + - id: python-check-mock-methods + - id: python-no-log-warn + - id: python-use-type-annotations + - id: rst-directive-colons + - id: rst-inline-touching-normal + - repo: https://github.com/psf/black + rev: 21.12b0 + hooks: + - id: black + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + args: ["--profile", "black"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e45601a35..9c1416a5bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,157 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [11.2.0] - 2022-02-08 + +### Added + +- Add support for US spelling of "gray" in ANSI color names https://github.com/Textualize/rich/issues/1890 +- Added `rich.diagnose.report` to expose environment debugging logic as function https://github.com/Textualize/rich/pull/1917 +- Added classmethod `Progress.get_default_columns()` to get the default list of progress bar columns https://github.com/Textualize/rich/pull/1894 + +### Fixed + +- Fixed performance issue in measuring text + +## [11.1.0] - 2022-01-28 + +### Added + +- Workaround for edge case of object from Faiss with no `__class__` https://github.com/Textualize/rich/issues/1838 +- Add Traditional Chinese readme +- Add `Syntax.guess_lexer`, add support for more lexers (e.g. Django templates etc.) https://github.com/Textualize/rich/pull/1869 +- Add `lexer` parameter to `Syntax.from_path` to allow for overrides https://github.com/Textualize/rich/pull/1873 + +### Fixed + +- Workaround for edge case of object from Faiss with no `__class__` https://github.com/Textualize/rich/issues/1838 +- Ensure `Syntax` always justifies left https://github.com/Textualize/rich/pull/1872 +- Handle classes in inspect when methods=True https://github.com/Textualize/rich/pull/1874 + +## [11.0.0] - 2022-01-09 + +### Added + +- Added max_depth arg to pretty printing https://github.com/Textualize/rich/issues/1585 +- Added `vertical_align` to Table.add_row https://github.com/Textualize/rich/issues/1590 + +### Fixed + +- Fixed issue with pretty repr in jupyter notebook https://github.com/Textualize/rich/issues/1717 +- Fix Traceback theme defaults override user supplied styles https://github.com/Textualize/rich/issues/1786 + +### Changed + +- **breaking** Deprecated rich.console.RenderGroup, now named rich.console.Group +- **breaking** `Syntax.__init__` parameter `lexer_name` renamed to `lexer` +- Syntax constructor accepts both str and now a pygments lexer https://github.com/Textualize/rich/pull/1748 + +## [10.16.2] - 2021-01-02 + +### Fixed + +- Fixed @ not being escaped in markup + +## [10.16.1] - 2021-12-15 + +### Fixed + +- Fixed issues with overlapping tags https://github.com/willmcgugan/rich/issues/1755 + +## [10.16.0] - 2021-12-12 + +### Fixed + +- Double print of progress bar in Jupyter https://github.com/willmcgugan/rich/issues/1737 + +### Added + +- Added Text.markup property https://github.com/willmcgugan/rich/issues/1751 + +## [10.15.2] - 2021-12-02 + +### Fixed + +- Deadlock issue https://github.com/willmcgugan/rich/issues/1734 + +## [10.15.1] - 2021-11-29 + +### Fixed + +- Reverted thread-safety fix for Live that introduced deadlock potential + +## [10.15.0] - 2021-11-28 + +### Added + +- Added dynamic_progress.py to examples +- Added ConsoleOptions.update_height +- Fixed Padding not respecting height + +### Changed + +- Some optimizations for simple strings (with only single cell widths) + +### Fixed + +- Fixed issue with progress bar not rendering markup https://github.com/willmcgugan/rich/issues/1721 +- Fixed race condition when exiting Live https://github.com/willmcgugan/rich/issues/1530 + +[10.15.0]: https://github.com/willmcgugan/rich/compare/v10.14.0...v10.15.0 + +## [10.14.0] - 2021-11-16 + +### Fixed + +- Fixed progress speed not updating when total doesn't change +- Fixed superfluous new line in Status https://github.com/willmcgugan/rich/issues/1662 +- Fixed Windows legacy width again +- Fixed infinite loop in set_cell_size https://github.com/willmcgugan/rich/issues/1682 + +### Added + +- Added file protocol to URL highlighter https://github.com/willmcgugan/rich/issues/1681 +- Added rich.protocol.rich_cast + +### Changed + +- Allowed `__rich__` to work recursively +- Allowed Text classes to work with sep in print https://github.com/willmcgugan/rich/issues/1689 + +### Added + +- Added a `rich.text.Text.from_ansi` helper method for handling pre-formatted input strings https://github.com/willmcgugan/rich/issues/1670 + +## [10.13.0] - 2021-11-07 + +### Added + +- Added json.dumps parameters to print_json https://github.com/willmcgugan/rich/issues/1638 + +### Fixed + +- Fixed an edge case bug when console module try to detect if they are in a tty at the end of a pytest run +- Fixed a bug where logging handler raises an exception when running with pythonw (related to https://bugs.python.org/issue13807) +- Fixed issue with TERM env vars that have more than one hyphen https://github.com/willmcgugan/rich/issues/1640 +- Fixed missing new line after progress bar when terminal is not interactive https://github.com/willmcgugan/rich/issues/1606 +- Fixed exception in IPython when disabling pprint with %pprint https://github.com/willmcgugan/rich/issues/1646 +- Fixed issue where values longer than the console width produced invalid JSON https://github.com/willmcgugan/rich/issues/1653 +- Fixes trailing comma when pretty printing dataclass with last field repr=False https://github.com/willmcgugan/rich/issues/1599 + +## Changed + +- Markdown codeblocks now word-wrap https://github.com/willmcgugan/rich/issues/1515 + ## [10.12.0] - 2021-10-06 ### Updated - Official Py3.10 release +### Fixed + +- Fixed detection of custom repr when pretty printing dataclasses + ## [10.11.0] - 2021-09-24 ### Added @@ -1445,7 +1590,7 @@ Major version bump for a breaking change to `Text.stylize signature`, which corr ### Fixed -- Readme links in Pypi +- Readme links in PyPI ## [0.4.0] - 2020-02-22 @@ -1484,3 +1629,143 @@ Major version bump for a breaking change to `Text.stylize signature`, which corr ### Added - First official release, API still to be stabilized + +[11.2.0]: https://github.com/willmcgugan/rich/compare/v11.1.0...v11.2.0 +[11.1.0]: https://github.com/willmcgugan/rich/compare/v11.0.0...v11.1.0 +[11.0.0]: https://github.com/willmcgugan/rich/compare/v10.16.1...v11.0.0 +[10.16.1]: https://github.com/willmcgugan/rich/compare/v10.16.0...v10.16.1 +[10.16.0]: https://github.com/willmcgugan/rich/compare/v10.15.2...v10.16.0 +[10.15.2]: https://github.com/willmcgugan/rich/compare/v10.15.1...v10.15.2 +[10.15.1]: https://github.com/willmcgugan/rich/compare/v10.15.0...v10.15.1 +[10.14.0]: https://github.com/willmcgugan/rich/compare/v10.14.0...v10.15.0 +[10.13.0]: https://github.com/willmcgugan/rich/compare/v10.13.0...v10.14.0 +[10.12.0]: https://github.com/willmcgugan/rich/compare/v10.12.0...v10.13.0 +[10.11.0]: https://github.com/willmcgugan/rich/compare/v10.11.0...v10.12.0 +[10.10.0]: https://github.com/willmcgugan/rich/compare/v10.10.0...v10.11.0 +[10.9.0]: https://github.com/willmcgugan/rich/compare/v10.9.0...v10.10.0 +[10.8.0]: https://github.com/willmcgugan/rich/compare/v10.8.0...v10.9.0 +[10.7.0]: https://github.com/willmcgugan/rich/compare/v10.7.0...v10.8.0 +[10.6.0]: https://github.com/willmcgugan/rich/compare/v10.6.0...v10.7.0 +[10.5.0]: https://github.com/willmcgugan/rich/compare/v10.5.0...v10.6.0 +[10.4.0]: https://github.com/willmcgugan/rich/compare/v10.4.0...v10.5.0 +[10.3.0]: https://github.com/willmcgugan/rich/compare/v10.3.0...v10.4.0 +[10.2.2]: https://github.com/willmcgugan/rich/compare/v10.2.2...v10.3.0 +[10.2.1]: https://github.com/willmcgugan/rich/compare/v10.2.1...v10.2.2 +[10.2.0]: https://github.com/willmcgugan/rich/compare/v10.2.0...v10.2.1 +[10.1.0]: https://github.com/willmcgugan/rich/compare/v10.1.0...v10.2.0 +[10.0.1]: https://github.com/willmcgugan/rich/compare/v10.0.1...v10.1.0 +[10.0.0]: https://github.com/willmcgugan/rich/compare/v10.0.0...v10.0.1 +[9.13.0]: https://github.com/willmcgugan/rich/compare/v9.13.0...v10.0.0 +[9.12.4]: https://github.com/willmcgugan/rich/compare/v9.12.4...v9.13.0 +[9.12.3]: https://github.com/willmcgugan/rich/compare/v9.12.3...v9.12.4 +[9.12.2]: https://github.com/willmcgugan/rich/compare/v9.12.2...v9.12.3 +[9.12.1]: https://github.com/willmcgugan/rich/compare/v9.12.1...v9.12.2 +[9.12.0]: https://github.com/willmcgugan/rich/compare/v9.12.0...v9.12.1 +[9.11.1]: https://github.com/willmcgugan/rich/compare/v9.11.1...v9.12.0 +[9.11.0]: https://github.com/willmcgugan/rich/compare/v9.11.0...v9.11.1 +[9.10.0]: https://github.com/willmcgugan/rich/compare/v9.10.0...v9.11.0 +[9.9.0]: https://github.com/willmcgugan/rich/compare/v9.9.0...v9.10.0 +[9.8.2]: https://github.com/willmcgugan/rich/compare/v9.8.2...v9.9.0 +[9.8.1]: https://github.com/willmcgugan/rich/compare/v9.8.1...v9.8.2 +[9.8.0]: https://github.com/willmcgugan/rich/compare/v9.8.0...v9.8.1 +[9.7.0]: https://github.com/willmcgugan/rich/compare/v9.7.0...v9.8.0 +[9.6.2]: https://github.com/willmcgugan/rich/compare/v9.6.2...v9.7.0 +[9.6.1]: https://github.com/willmcgugan/rich/compare/v9.6.1...v9.6.2 +[9.6.0]: https://github.com/willmcgugan/rich/compare/v9.6.0...v9.6.1 +[9.5.1]: https://github.com/willmcgugan/rich/compare/v9.5.1...v9.6.0 +[9.5.0]: https://github.com/willmcgugan/rich/compare/v9.5.0...v9.5.1 +[9.4.0]: https://github.com/willmcgugan/rich/compare/v9.4.0...v9.5.0 +[9.3.0]: https://github.com/willmcgugan/rich/compare/v9.3.0...v9.4.0 +[9.2.0]: https://github.com/willmcgugan/rich/compare/v9.2.0...v9.3.0 +[9.1.0]: https://github.com/willmcgugan/rich/compare/v9.1.0...v9.2.0 +[9.0.1]: https://github.com/willmcgugan/rich/compare/v9.0.1...v9.1.0 +[9.0.0]: https://github.com/willmcgugan/rich/compare/v9.0.0...v9.0.1 +[8.0.0]: https://github.com/willmcgugan/rich/compare/v8.0.0...v9.0.0 +[7.1.0]: https://github.com/willmcgugan/rich/compare/v7.1.0...v8.0.0 +[7.0.0]: https://github.com/willmcgugan/rich/compare/v7.0.0...v7.1.0 +[6.2.0]: https://github.com/willmcgugan/rich/compare/v6.2.0...v7.0.0 +[6.1.2]: https://github.com/willmcgugan/rich/compare/v6.1.2...v6.2.0 +[6.1.1]: https://github.com/willmcgugan/rich/compare/v6.1.1...v6.1.2 +[6.1.0]: https://github.com/willmcgugan/rich/compare/v6.1.0...v6.1.1 +[6.0.0]: https://github.com/willmcgugan/rich/compare/v6.0.0...v6.1.0 +[5.2.1]: https://github.com/willmcgugan/rich/compare/v5.2.1...v6.0.0 +[5.2.0]: https://github.com/willmcgugan/rich/compare/v5.2.0...v5.2.1 +[5.1.2]: https://github.com/willmcgugan/rich/compare/v5.1.2...v5.2.0 +[5.1.1]: https://github.com/willmcgugan/rich/compare/v5.1.1...v5.1.2 +[5.1.0]: https://github.com/willmcgugan/rich/compare/v5.1.0...v5.1.1 +[5.0.0]: https://github.com/willmcgugan/rich/compare/v5.0.0...v5.1.0 +[4.2.2]: https://github.com/willmcgugan/rich/compare/v4.2.2...v5.0.0 +[4.2.1]: https://github.com/willmcgugan/rich/compare/v4.2.1...v4.2.2 +[4.2.0]: https://github.com/willmcgugan/rich/compare/v4.2.0...v4.2.1 +[4.1.0]: https://github.com/willmcgugan/rich/compare/v4.1.0...v4.2.0 +[4.0.0]: https://github.com/willmcgugan/rich/compare/v4.0.0...v4.1.0 +[3.4.1]: https://github.com/willmcgugan/rich/compare/v3.4.1...v4.0.0 +[3.4.0]: https://github.com/willmcgugan/rich/compare/v3.4.0...v3.4.1 +[3.3.2]: https://github.com/willmcgugan/rich/compare/v3.3.2...v3.4.0 +[3.3.1]: https://github.com/willmcgugan/rich/compare/v3.3.1...v3.3.2 +[3.3.0]: https://github.com/willmcgugan/rich/compare/v3.3.0...v3.3.1 +[3.2.0]: https://github.com/willmcgugan/rich/compare/v3.2.0...v3.3.0 +[3.1.0]: https://github.com/willmcgugan/rich/compare/v3.1.0...v3.2.0 +[3.0.5]: https://github.com/willmcgugan/rich/compare/v3.0.5...v3.1.0 +[3.0.4]: https://github.com/willmcgugan/rich/compare/v3.0.4...v3.0.5 +[3.0.3]: https://github.com/willmcgugan/rich/compare/v3.0.3...v3.0.4 +[3.0.2]: https://github.com/willmcgugan/rich/compare/v3.0.2...v3.0.3 +[3.0.1]: https://github.com/willmcgugan/rich/compare/v3.0.1...v3.0.2 +[3.0.0]: https://github.com/willmcgugan/rich/compare/v3.0.0...v3.0.1 +[2.3.1]: https://github.com/willmcgugan/rich/compare/v2.3.1...v3.0.0 +[2.3.0]: https://github.com/willmcgugan/rich/compare/v2.3.0...v2.3.1 +[2.2.6]: https://github.com/willmcgugan/rich/compare/v2.2.6...v2.3.0 +[2.2.5]: https://github.com/willmcgugan/rich/compare/v2.2.5...v2.2.6 +[2.2.4]: https://github.com/willmcgugan/rich/compare/v2.2.4...v2.2.5 +[2.2.3]: https://github.com/willmcgugan/rich/compare/v2.2.3...v2.2.4 +[2.2.2]: https://github.com/willmcgugan/rich/compare/v2.2.2...v2.2.3 +[2.2.1]: https://github.com/willmcgugan/rich/compare/v2.2.1...v2.2.2 +[2.2.0]: https://github.com/willmcgugan/rich/compare/v2.2.0...v2.2.1 +[2.1.0]: https://github.com/willmcgugan/rich/compare/v2.1.0...v2.2.0 +[2.0.1]: https://github.com/willmcgugan/rich/compare/v2.0.1...v2.1.0 +[2.0.0]: https://github.com/willmcgugan/rich/compare/v2.0.0...v2.0.1 +[1.3.1]: https://github.com/willmcgugan/rich/compare/v1.3.1...v2.0.0 +[1.3.0]: https://github.com/willmcgugan/rich/compare/v1.3.0...v1.3.1 +[1.2.3]: https://github.com/willmcgugan/rich/compare/v1.2.3...v1.3.0 +[1.2.2]: https://github.com/willmcgugan/rich/compare/v1.2.2...v1.2.3 +[1.2.1]: https://github.com/willmcgugan/rich/compare/v1.2.1...v1.2.2 +[1.2.0]: https://github.com/willmcgugan/rich/compare/v1.2.0...v1.2.1 +[1.1.9]: https://github.com/willmcgugan/rich/compare/v1.1.9...v1.2.0 +[1.1.8]: https://github.com/willmcgugan/rich/compare/v1.1.8...v1.1.9 +[1.1.7]: https://github.com/willmcgugan/rich/compare/v1.1.7...v1.1.8 +[1.1.6]: https://github.com/willmcgugan/rich/compare/v1.1.6...v1.1.7 +[1.1.5]: https://github.com/willmcgugan/rich/compare/v1.1.5...v1.1.6 +[1.1.4]: https://github.com/willmcgugan/rich/compare/v1.1.4...v1.1.5 +[1.1.3]: https://github.com/willmcgugan/rich/compare/v1.1.3...v1.1.4 +[1.1.2]: https://github.com/willmcgugan/rich/compare/v1.1.2...v1.1.3 +[1.1.1]: https://github.com/willmcgugan/rich/compare/v1.1.1...v1.1.2 +[1.1.0]: https://github.com/willmcgugan/rich/compare/v1.1.0...v1.1.1 +[1.0.3]: https://github.com/willmcgugan/rich/compare/v1.0.3...v1.1.0 +[1.0.2]: https://github.com/willmcgugan/rich/compare/v1.0.2...v1.0.3 +[1.0.1]: https://github.com/willmcgugan/rich/compare/v1.0.1...v1.0.2 +[1.0.0]: https://github.com/willmcgugan/rich/compare/v1.0.0...v1.0.1 +[0.8.13]: https://github.com/willmcgugan/rich/compare/v0.8.13...v1.0.0 +[0.8.12]: https://github.com/willmcgugan/rich/compare/v0.8.12...v0.8.13 +[0.8.11]: https://github.com/willmcgugan/rich/compare/v0.8.11...v0.8.12 +[0.8.10]: https://github.com/willmcgugan/rich/compare/v0.8.10...v0.8.11 +[0.8.9]: https://github.com/willmcgugan/rich/compare/v0.8.9...v0.8.10 +[0.8.8]: https://github.com/willmcgugan/rich/compare/v0.8.8...v0.8.9 +[0.8.7]: https://github.com/willmcgugan/rich/compare/v0.8.7...v0.8.8 +[0.8.6]: https://github.com/willmcgugan/rich/compare/v0.8.6...v0.8.7 +[0.8.5]: https://github.com/willmcgugan/rich/compare/v0.8.5...v0.8.6 +[0.8.4]: https://github.com/willmcgugan/rich/compare/v0.8.4...v0.8.5 +[0.8.3]: https://github.com/willmcgugan/rich/compare/v0.8.3...v0.8.4 +[0.8.2]: https://github.com/willmcgugan/rich/compare/v0.8.2...v0.8.3 +[0.8.1]: https://github.com/willmcgugan/rich/compare/v0.8.1...v0.8.2 +[0.8.0]: https://github.com/willmcgugan/rich/compare/v0.8.0...v0.8.1 +[0.7.2]: https://github.com/willmcgugan/rich/compare/v0.7.2...v0.8.0 +[0.7.1]: https://github.com/willmcgugan/rich/compare/v0.7.1...v0.7.2 +[0.7.0]: https://github.com/willmcgugan/rich/compare/v0.7.0...v0.7.1 +[0.6.0]: https://github.com/willmcgugan/rich/compare/v0.6.0...v0.7.0 +[0.5.0]: https://github.com/willmcgugan/rich/compare/v0.5.0...v0.6.0 +[0.4.1]: https://github.com/willmcgugan/rich/compare/v0.4.1...v0.5.0 +[0.4.0]: https://github.com/willmcgugan/rich/compare/v0.4.0...v0.4.1 +[0.3.3]: https://github.com/willmcgugan/rich/compare/v0.3.3...v0.4.0 +[0.3.2]: https://github.com/willmcgugan/rich/compare/v0.3.2...v0.3.3 +[0.3.1]: https://github.com/willmcgugan/rich/compare/v0.3.1...v0.3.2 +[0.3.0]: https://github.com/willmcgugan/rich/compare/v0.3.0...v0.3.1 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c3dc1983cc..0c0f891fdc 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -4,16 +4,27 @@ The following people have contributed to the development of Rich: +- [Gregory Beauregard](https://github.com/GBeauregard/pyffstream) +- [Darren Burns](https://github.com/darrenburns) - [Pete Davison](https://github.com/pd93) +- [James Estevez](https://github.com/jstvz) - [Oleksis Fraga](https://github.com/oleksis) +- [Kenneth Hoste](https://github.com/boegel) - [Finn Hughes](https://github.com/finnhughes) - [Josh Karpel](https://github.com/JoshKarpel) +- [Andrew Kettmann](https://github.com/akettmann) - [Hedy Li](https://github.com/hedythedev) - [Alexander Mancevice](https://github.com/amancevice) - [Will McGugan](https://github.com/willmcgugan) +- [Paul McGuire](https://github.com/ptmcg) - [Nathan Page](https://github.com/nathanrpage97) +- [Avi Perl](https://github.com/avi-perl) +- [Laurent Peuch](https://github.com/psycojoker) - [Kylian Point](https://github.com/p0lux) - [Kyle Pollina](https://github.com/kylepollina) - [Clément Robert](https://github.com/neutrinoceros) - [Tushar Sadhwani](https://github.com/tusharsadhwani) +- [Tim Savage](https://github.com/timsavage) +- [Nicolas Simonds](https://github.com/0xDEC0DE) - [Gabriele N. Tornetta](https://github.com/p403n1x87) +- [Patrick Arminio](https://github.com/patrick91) diff --git a/LICENSE b/LICENSE index 8d9e799c3d..4415505566 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,19 @@ -Copyright 2020 Will McGugan +Copyright (c) 2020 Will McGugan -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile index 56977d2c8c..17ede92638 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ test: - pytest --cov-report term-missing --cov=rich tests/ -vv + TERM=unknown pytest --cov-report term-missing --cov=rich tests/ -vv format-check: black --check . format: diff --git a/README.cn.md b/README.cn.md index 433b3069ff..a5c7c5b66a 100644 --- a/README.cn.md +++ b/README.cn.md @@ -7,7 +7,8 @@ ![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) [English readme](https://github.com/willmcgugan/rich/blob/master/README.md) - • [中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) @@ -17,6 +18,8 @@ • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) Rich 是一个 Python 库,可以为您在终端中提供富文本和精美格式。 @@ -36,10 +39,10 @@ Rich 还可以与[Jupyter 笔记本](https://jupyter.org/)一起使用,而无 ## 安装说明 -使用`pip`或其他 PyPi 软件包管理器进行安装。 +使用`pip`或其他 PyPI 软件包管理器进行安装。 -``` -pip install rich +```sh +python -m pip install rich ``` ## Rich 的打印功能 diff --git a/README.de-ch.md b/README.de-ch.md index 5c92cab937..6c1d7d278b 100644 --- a/README.de-ch.md +++ b/README.de-ch.md @@ -7,7 +7,8 @@ ![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) [English readme](https://github.com/willmcgugan/rich/blob/master/README.md) - • [中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) @@ -17,6 +18,8 @@ • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) Rich isch ä Python Library för _rich_ Text ond ganz schöni formatiärig im Törminäl @@ -30,21 +33,21 @@ Lueg was [anderi öber Rich säged](https://www.willmcgugan.com/blog/pages/post/ ## Kompatibilität -Rich funktioniert mit Linux, OSX ond Windows. True color / emoji funktioniert mit em neue Windows Törminäl, s klassische Törminäl isch of 16 Farbe limitiärt. Rich brucht Pzthon 3.6.1 oder neuer. +Rich funktioniert mit Linux, OSX ond Windows. True color / emoji funktioniert mit em neue Windows Törminäl, s klassische Törminäl isch of 16 Farbe limitiärt. Rich brucht Python 3.6.1 oder neuer. Rich funktioniert mit [Jupyter notebooks](https://jupyter.org/) ohni irgendwelchä zuäsätzloche konfiguration. ## Installation -Installation mit `pip` oder mit dim liäblings PyPi Päckli-Mangager. +Installation mit `pip` oder mit dim liäblings PyPI Päckli-Manager. -``` -pip install rich +```sh +python -m pip install rich ``` Für das do us zum d Rich usgob im Törminäl z teste: -``` +```sh python -m rich ``` @@ -123,7 +126,7 @@ Rich hät e [inspect](https://rich.readthedocs.io/en/latest/reference/init.html? Lueg do d [inspect Dokumentation](https://rich.readthedocs.io/en/latest/reference/init.html#rich.inspect) für d Details a. -# Rich Library +# Rich-Bibliothek Rich häd ä Aazahl vo integriäte _renderables_ wo du chasch verwende zum eleganti Usgobe i dinner e CLI generiäre ond der hälfed bim debugge vo dim Code. @@ -132,7 +135,7 @@ Drock of di folgende Öberschrifte für d Details:
Log -S Console Objekt hät e `log()` Methode wo verglichbar zu de `print()` Schnittstell isch aber zuäsätzloch no e Spaltä för di aktuäll Zitt und d Datai mit de Zille wo de Ufruäf macht us git. Standartmässig tuät Rich es Syntax Highlighting für Python Strukturä sowiä repr Text machä. Went e Collection (wiä zum Bispiil dict oder list) loggsch wird Rich das hübsch Usgeh so dass es i de verfüägbari Platz ine passt. Do es Bispiil für e paar vo dene Funktionä. +S Console Objekt hät e `log()` Methode wo verglichbar zu de `print()` Schnittstell isch aber zuäsätzloch no e Spaltä för di aktuäll Zitt und d Datai mit de Zille wo de Ufruäf macht us git. Standardmässig tuät Rich es Syntax Highlighting für Python Strukturä sowiä repr Text machä. Went e Collection (wiä zum Bispiil dict oder list) loggsch wird Rich das hübsch Usgeh so dass es i de verfüägbari Platz ine passt. Do es Bispiil für e paar vo dene Funktionä. ```python from rich.console import Console @@ -196,9 +199,9 @@ Rich cha flexiibäl [Tabelle](https://rich.readthedocs.io/en/latest/tables.html) ![table movie](https://github.com/willmcgugan/rich/raw/master/imgs/table_movie.gif) -D Animation obe isch mit [table_movie.py](https://github.com/willmcgugan/rich/blob/master/examples/table_movie.py) us em Bispiil Ordner erstellt worde. +D Animation obe isch mit [table_movie.py](https://github.com/willmcgugan/rich/blob/master/examples/table_movie.py) us em Bispiil-Ordner erstellt worde. -Do es eifachs Tabelle Bispiil: +Do es eifachs Tabelle-Bispiil: ```python from rich.console import Console @@ -297,7 +300,7 @@ D Spinner Animatione sind vo [cli-spinners](https://www.npmjs.com/package/cli-sp python -m rich.spinner ``` -De Befehlt obe generiärt di folgändi Usgob im Törminäl: +De Befehl obe generiärt di folgändi Usgob im Törminäl: ![spinners](https://github.com/willmcgugan/rich/raw/master/imgs/spinners.gif) @@ -323,9 +326,9 @@ Lueg s Bispiil Script [tree.py](https://github.com/willmcgugan/rich/blob/master/
-Columns +Spaltene -Rich can render content in neat [columns](https://rich.readthedocs.io/en/latest/columns.html) with equal or optimal width. Here's a very basic clone of the (MacOS / Linux) `ls` command which displays a directory listing in columns: +Rich cha Inhalt i hübsche [Spaltene](https://rich.readthedocs.io/en/latest/columns.html) darstelle mit glichä oder optimale Breiti. Do isch e ganz eifachi kopii vom (MacOS / Linux) `ls` Befehl wo Ordner in Spaltene darstellt ```python import os @@ -338,7 +341,7 @@ directory = os.listdir(sys.argv[1]) print(Columns(directory)) ``` -De folgend Screenshot isch d Usgob vom [columns example](https://github.com/willmcgugan/rich/blob/master/examples/columns.py) Bispiil, wo Date vonnere API hollt ond in Colums darstellt: +De folgend Screenshot isch d Usgob vom [Spalte-Bispiil](https://github.com/willmcgugan/rich/blob/master/examples/columns.py), wo Date vonnere API hollt ond in Spaltene darstellt: ![columns](https://github.com/willmcgugan/rich/raw/master/imgs/columns.png) @@ -349,7 +352,7 @@ De folgend Screenshot isch d Usgob vom [columns example](https://github.com/will Rich cha [markdown](https://rich.readthedocs.io/en/latest/markdown.html) übersetze ond leistet vernünftigi Ärbät bim formatiärige is Törminäl z übersetze. -Zum Markdonw z übersetze importier d Klass `Markdown` und instanzier es mitem Markdown Text. Nocher gid mos uf de Konsolä us. Do es Bispiil: +Zum Markdown z übersetze importier d Klass `Markdown` und instanzier es mitem Markdown Text. Nocher gid mos uf de Konsolä us. Do es Bispiil: ```python from rich.console import Console @@ -405,7 +408,7 @@ Das wird d Usgob ungefär wie s Folgende geh:
Tracebacks -Rich cha [wunderschöni Tracebacks](https://rich.readthedocs.io/en/latest/traceback.html) generiäre wo eifach zum läse sind und meh Code als de standart Python Traceback darstellt. Du chasch Rich als default Traceback Handler setzä ond alli nöd abfangene Exceptions werded mit Rich dargstellt. +Rich cha [wunderschöni Tracebacks](https://rich.readthedocs.io/en/latest/traceback.html) generiäre wo eifach zum läse sind und meh Code als de Standard-Python-Traceback darstellt. Du chasch Rich als default Traceback Handler setzä ond alli nöd abfangene Exceptions werded mit Rich dargstellt. So gsiets ungefär ufemen OSX (ähnloch uf Linux) us: @@ -413,7 +416,7 @@ So gsiets ungefär ufemen OSX (ähnloch uf Linux) us:
-Alli Rich Renderables bruched s [Console Protokoll](https://rich.readthedocs.io/en/latest/protocol.html), wo mo au für d eige entwicklig vo Rich Inhalt cha bruche. +Alli Rich Renderables bruched s [Console-Protokoll](https://rich.readthedocs.io/en/latest/protocol.html), wo mo au für d eige Entwicklig vo Rich-Inhalt cha bruche. # Rich für Ondernemä @@ -449,6 +452,6 @@ Do es paar Projekt wo Rich verwended: Lightweight Python library for adding real-time 2D object tracking to any detector. - [ansible/ansible-lint](https://github.com/ansible/ansible-lint) Ansible-lint checks playbooks for practices and behaviour that could potentially be improved - [ansible-community/molecule](https://github.com/ansible-community/molecule) Ansible Molecule testing framework -- +[Many more](https://github.com/willmcgugan/rich/network/dependents)! +- +[Vieli meh](https://github.com/willmcgugan/rich/network/dependents)! diff --git a/README.de.md b/README.de.md index c7ed937cf6..34a99a64e8 100644 --- a/README.de.md +++ b/README.de.md @@ -7,7 +7,8 @@ ![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) [English readme](https://github.com/willmcgugan/rich/blob/master/README.md) - • [中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) @@ -17,6 +18,8 @@ • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) Rich ist eine Python-Bibliothek für _rich_ Text und schöne Formatierung im Terminal. @@ -36,15 +39,15 @@ Rich funktioniert ohne zusätzliche Konfiguration mit [Jupyter Notebooks](https: ## Installation -Installation mit `pip` oder deinem bevorzugten PyPi-Paketmanager. +Installation mit `pip` oder deinem bevorzugten PyPI-Paketmanager. -``` -python -m rich +```sh +python -m pip install rich ``` Führe die folgenden Schritte aus, um die Rich-Ausgabe auf deinem Terminal zu testen: -``` +```sh python -m rich ``` diff --git a/README.es.md b/README.es.md index 9588336e3b..c9cb2af141 100644 --- a/README.es.md +++ b/README.es.md @@ -7,7 +7,8 @@ ![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) [English readme](https://github.com/willmcgugan/rich/blob/master/README.md) - • [中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) @@ -17,6 +18,8 @@ • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) Rich es un paquete de Python para texto _enriquecido_ y un hermoso formato en la terminal. @@ -36,15 +39,15 @@ Rich funciona con [Jupyter notebooks](https://jupyter.org/) sin necesidad de con ## Instalación -Instale con `pip` o su administrador de paquetes PyPi favorito. +Instale con `pip` o su administrador de paquetes PyPI favorito. -``` -pip install rich +```sh +python -m pip install rich ``` Ejecute lo siguiente para probar la salida de Rich sobre su terminal: -``` +```sh python -m rich ``` diff --git a/README.fr.md b/README.fr.md index 73abe604e0..1a09c054a1 100644 --- a/README.fr.md +++ b/README.fr.md @@ -7,7 +7,8 @@ ![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) [English readme](https://github.com/willmcgugan/rich/blob/master/README.md) - • [中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) @@ -17,6 +18,8 @@ • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) Rich est une bibliothèque Python pour le _rich_ texte et la mise en forme dans le terminal. @@ -36,15 +39,15 @@ Rich fonctionne avec les notebooks Jupyter sans configuration supplémentaire. ## Installation -Installez avec `pip` ou votre gestionnaire de paquets Pypi préféré. +Installez avec `pip` ou votre gestionnaire de paquets PyPI préféré. -``` -pip install rich +```sh +python -m pip install rich ``` Exécutez ce qui suit pour tester la sortie de Rich sur votre terminal : -``` +```sh python -m rich ``` diff --git a/README.hi.md b/README.hi.md index 420edd8fcb..c3c62f5321 100644 --- a/README.hi.md +++ b/README.hi.md @@ -7,7 +7,8 @@ ![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) [English readme](https://github.com/willmcgugan/rich/blob/master/README.md) - • [中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) @@ -17,6 +18,8 @@ • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) Rich टर्मिनल में _समृद्ध_ पाठ और सुंदर स्वरूपण के लिए एक Python संग्रह है। @@ -40,12 +43,12 @@ Rich बिना किसी अतिरिक्त विन्यास `pip` या अपने पसंदीदा PyPI संकुल प्रबंधक (package manager) के द्वारा आप इसे स्थापित कर सकते हैं। -``` -pip install rich +```sh +python -m pip install rich ``` आपके टर्मिनल पर Rich उत्पादन का परीक्षण करने के लिए यह चलाएं: -``` +```sh python -m rich ``` diff --git a/README.it.md b/README.it.md new file mode 100644 index 0000000000..c6e8b5620c --- /dev/null +++ b/README.it.md @@ -0,0 +1,457 @@ +[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich) +[![codecov](https://codecov.io/gh/willmcgugan/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/willmcgugan/rich) +[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/) +[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan) + +![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) + +[English readme](https://github.com/willmcgugan/rich/blob/master/README.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) + • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) + • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) + • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) + • [日本語 readme](https://github.com/willmcgugan/rich/blob/master/README.ja.md) + • [한국어 readme](https://github.com/willmcgugan/rich/blob/master/README.kr.md) + • [Français readme](https://github.com/willmcgugan/rich/blob/master/README.fr.md) + • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) + • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) + • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) + + +Rich è una libreria Python per un testo _rich_ e con una piacevole formattazione nel terminale. + +Le [Rich API](https://rich.readthedocs.io/en/latest/) permettono di aggiungere facilmente colore e stile all'output del terminale. Rich permette di visualizzare tabelle, barre di avanzamento, markdown, evidenziazione della sintassi, tracebacks, e molto altro ancora — tutto già pronto all'uso. + +![Features](https://github.com/willmcgugan/rich/raw/master/imgs/features.png) + +Per una video-introduzione di Rich puoi vedere [calmcode.io](https://calmcode.io/rich/introduction.html) by [@fishnets88](https://twitter.com/fishnets88). + +Guarda cosa [le persone dicono su Rich](https://www.willmcgugan.com/blog/pages/post/rich-tweets/). + +## Compatibilità + +Rich funziona su Linux, OSX, e Windows. True color / emoji funzionano con il nuovo Windows Terminal, il terminale classico è limitato a 16 colori. Rich richiede Python 3.6.1 o superiore. + +Rich funziona con i [Jupyter notebooks](https://jupyter.org/) senza configurazioni aggiuntive. + +## Installazione + +Installa con `pip` o il tuo PyPI package manager preferito. + +```sh +python -m pip install rich +``` + +Esegui il seguente comando per testare l'output di Rich sul tuo terminale: + +```sh +python -m rich +``` + +## Rich Print + +Utilizzare rich è semplicissimo, ti basta importare il metodo [rich print](https://rich.readthedocs.io/en/latest/introduction.html#quick-start), che ha la stessa signature della funzione builtin in Python. Prova: + +```python +from rich import print + +print("Hello, [bold magenta]World[/bold magenta]!", ":vampire:", locals()) +``` + +![Hello World](https://github.com/willmcgugan/rich/raw/master/imgs/print.png) + +## Rich REPL + +Rich può essere installo in Python REPL, in questo modo ogni struttura dati sarà visualizzata in modo gradevole ed evidenziato. + +```python +>>> from rich import pretty +>>> pretty.install() +``` + +![REPL](https://github.com/willmcgugan/rich/raw/master/imgs/repl.png) + +## Utilizzo di Console + +Per un maggiore personalizzazione dei contenuti puoi importare ed instanziare un oggetto [Console](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console). + +```python +from rich.console import Console + +console = Console() +``` + +L'oggetto Console ha il metodo `print` che utilizza volutamente un interfaccia simile a quella del `print` originale. Ad esempio: + +```python +console.print("Hello", "World!") +``` + +Come puoi immaginare, questo stamperà `"Hello World!"` sul terminale. Nota che diversamente dalla funzione builtin `print`, Rich potrebbe portare a capo il testo per rispettare le dimensioni del terminale. + +Ci sono diversi modi di aggiungere stile e colore al tuo output. Puoi impostare uno stile per l'intero output utilizzando l'argomento keyword `style`. Ad esempio: + +```python +console.print("Hello", "World!", style="bold red") +``` + +L'output sarà qualcosa tipo: + +![Hello World](https://github.com/willmcgugan/rich/raw/master/imgs/hello_world.png) + +Questo va bene per applicare uno stile ad una linea di testo alla volta. Per uno stile più ricercato, puoi utilizzare uno speciale linguaggio di markup che è simile nella sintassi a [bbcode](https://en.wikipedia.org/wiki/BBCode). Ad esempio: + +```python +console.print("Where there is a [bold cyan]Will[/bold cyan] there [u]is[/u] a [i]way[/i].") +``` + +![Console Markup](https://github.com/willmcgugan/rich/raw/master/imgs/where_there_is_a_will.png) + +Puoi utilizzare l'oggetto Console per generare output sofisticati con il minimo sforzo. Vedi la docs di [Console API](https://rich.readthedocs.io/en/latest/console.html) per ulteriori dettagli. + +## Rich Inspect + +Rich ha una funzione [inspect](https://rich.readthedocs.io/en/latest/reference/init.html?highlight=inspect#rich.inspect) che può produrre un report per un qualsiasi oggetto Python, come una classe, un instanza, o un builtin. + +```python +>>> my_list = ["foo", "bar"] +>>> from rich import inspect +>>> inspect(my_list, methods=True) +``` + +![Log](https://github.com/willmcgugan/rich/raw/master/imgs/inspect.png) + +Vedi [inspect docs](https://rich.readthedocs.io/en/latest/reference/init.html#rich.inspect) per ulteriori dettagli. + +# Rich Library + +Rich contiene alcuni builtin _renderables_ che puoi utilizzare per creare eleganti output nella tua CLI e aiutarti nel debug del tuo codice. + +Fai click sulle seguenti intestazioni per ulteriori dettagli: + +
+Log + +L'oggetto Console ha un metodo `log()` che utilizza un'interfaccia simile a `print()`, ma visualizza anche una colonna con l'ora corrente, il file e la linea che hanno generato la chiamata. Di default Rich evidenzierà le strutture Python e le stringhe repr. Se logghi un oggetto di tipo collection (e.s. un dict o una lista) Rich automaticamente abbellirà l'output in modo che possa entrare nello spazio disponibile. Ecco qui un esempio di alcune delle feature discusse: + +```python +from rich.console import Console +console = Console() + +test_data = [ + {"jsonrpc": "2.0", "method": "sum", "params": [None, 1, 2, 4, False, True], "id": "1",}, + {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}, + {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "2"}, +] + +def test_log(): + enabled = False + context = { + "foo": "bar", + } + movies = ["Deadpool", "Rise of the Skywalker"] + console.log("Hello from", console, "!") + console.log(test_data, log_locals=True) + + +test_log() +``` + +Il codice appena mostrato produce il seguente output: + +![Log](https://github.com/willmcgugan/rich/raw/master/imgs/log.png) + +Nota l'argomento `log_locals`, che visualizza una tabella contenente le variabili locali dove il metodo log è stato chiamato. + +Il metodo log può essere usato per il logging su terminale di applicazioni che solitamente girano su server, ma ha anche uno scopo orientato al debugging. + +
+
+Logging Handler + +Puoi anche utilizzare la classe builtin [Handler](https://rich.readthedocs.io/en/latest/logging.html) per formattare e colorare l'output dal modulo logging di Python. Ecco un esempio dell'output: + +![Logging](https://github.com/willmcgugan/rich/raw/master/imgs/logging.png) + +
+ +
+Emoji + +Per inserire un emoji nell'output della console inseriscine il nome in mezzo a due ':'. Ad esempio: + +```python +>>> console.print(":smiley: :vampire: :pile_of_poo: :thumbs_up: :raccoon:") +😃 🧛 💩 👍 🦝 +``` + +Usa questa feature saggiamente. + +
+ +
+Tables + +Rich può visualizzare [tabelle](https://rich.readthedocs.io/en/latest/tables.html) flessibili con caratteri unicode. C'è una vasta gamma di opzioni per la formattazione di bordi, stili, allineamenti di celle etc. + +![table movie](https://github.com/willmcgugan/rich/raw/master/imgs/table_movie.gif) + +Questa animazione è stata realizzata con [table_movie.py](https://github.com/willmcgugan/rich/blob/master/examples/table_movie.py) presente nella directory examples. + +Ecco qui un semplice esempio di tabella: + +```python +from rich.console import Console +from rich.table import Table + +console = Console() + +table = Table(show_header=True, header_style="bold magenta") +table.add_column("Date", style="dim", width=12) +table.add_column("Title") +table.add_column("Production Budget", justify="right") +table.add_column("Box Office", justify="right") +table.add_row( + "Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$275,000,000", "$375,126,118" +) +table.add_row( + "May 25, 2018", + "[red]Solo[/red]: A Star Wars Story", + "$275,000,000", + "$393,151,347", +) +table.add_row( + "Dec 15, 2017", + "Star Wars Ep. VIII: The Last Jedi", + "$262,000,000", + "[bold]$1,332,539,889[/bold]", +) + +console.print(table) +``` + +Questo produce il seguente output: + +![table](https://github.com/willmcgugan/rich/raw/master/imgs/table.png) + +Nota che il console markup è visualizzato nello stesso modo di `print()` e `log()`. Infatti, tutto ciò che è visualizzabile da Rich può essere incluso nelle intestazioni / righe (anche altre tabelle). + +La classe `Table` è abbastanza smart da ridimensionare le colonne per entrare nello spazio residuo del terminale, wrappando il testo come richiesto. Ad esempio, con il terminale reso più piccolo della tabella sopra: + +![table2](https://github.com/willmcgugan/rich/raw/master/imgs/table2.png) + +
+ +
+Barre di avanzamento + +Rich può visualizzare, senza sfarfallio, multiple barre [di avanzamento](https://rich.readthedocs.io/en/latest/progress.html) per tenere traccia di task di lunga durata. + +Per un utilizzo base, wrappa ogni 'step' con la funzione `track` e itera sul risultato. Ad esempio: + +```python +from rich.progress import track + +for step in track(range(100)): + do_step(step) +``` + +Non è difficile aggiungere barre di avanzamento multiple. Ecco un esempio dalla documentazione: + +![progress](https://github.com/willmcgugan/rich/raw/master/imgs/progress.gif) + +Le colonne possono essere configurate per visualizzare qualsiasi dettaglio tu voglia. Le colonne built-in includono percentuale di completamente, dimensione del file, velocità, e tempo rimasto. Ecco un altro esempio che mostra un download in corso: + +![progress](https://github.com/willmcgugan/rich/raw/master/imgs/downloader.gif) + +Per testare tu stesso, vedi [examples/downloader.py](https://github.com/willmcgugan/rich/blob/master/examples/downloader.py) che può scaricare multipli URL simultaneamente mentre mostra lo stato di avanzamento. + +
+ +
+Status + +Per situazioni in cui è difficile calcolare l'avanzamento, puoi utilizzare il metodo [status](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console.status) che mostrerà un animazione 'spinner' e un messaggio. L'animazione non ti impedisce di utilizzare la console normalmente. Ad esempio: + +```python +from time import sleep +from rich.console import Console + +console = Console() +tasks = [f"task {n}" for n in range(1, 11)] + +with console.status("[bold green]Working on tasks...") as status: + while tasks: + task = tasks.pop(0) + sleep(1) + console.log(f"{task} complete") +``` + +Questo produrrà il seguente output nel terminale. + +![status](https://github.com/willmcgugan/rich/raw/master/imgs/status.gif) + +L'animazione dello spinner è ispirata da [cli-spinners](https://www.npmjs.com/package/cli-spinners). Puoi selezionarne uno specificando `spinner` tra i parametri. Esegui il seguente comando per visualizzare le possibili opzioni: + +```shell +python -m rich.spinner +``` + +Questo produrrà il seguente output nel terminale. + +![spinners](https://github.com/willmcgugan/rich/raw/master/imgs/spinners.gif) + +
+ +
+Albero + +Rich può visualizzare un [albero](https://rich.readthedocs.io/en/latest/tree.html) con linee guida. Un albero è ideale per mostrare la struttura di un file, o altri dati gerarchici. + +Le etichette dell'albero possono essere semplice testo o qualsiasi altra cosa che Rich può visualizzare. Esegui il seguente comando per una dimostrazione: + +```shell +python -m rich.tree +``` + +Questo produrrà il seguente output: + +![markdown](https://github.com/willmcgugan/rich/raw/master/imgs/tree.png) + +Vedi l'esempio [tree.py](https://github.com/willmcgugan/rich/blob/master/examples/tree.py) per uno script che mostra una vista ad albero di ogni directory, simile a quella del comando linux `tree`. + +
+ +
+Colonne + +Rich può visualizzare contenuti in [colonne](https://rich.readthedocs.io/en/latest/columns.html) ordinate con larghezza uguale o ottimale. Ecco qui un clone base del comando (MacOS / Linux) `ls` che mostra il contenuto di una directory in colonna: + +```python +import os +import sys + +from rich import print +from rich.columns import Columns + +directory = os.listdir(sys.argv[1]) +print(Columns(directory)) +``` + +Il seguente screenshot è l'output dell'[esempio di columns](https://github.com/willmcgugan/rich/blob/master/examples/columns.py) che visualizza i dati ottenuti da un API in colonna: + +![columns](https://github.com/willmcgugan/rich/raw/master/imgs/columns.png) + +
+ +
+Markdown + +Rich può visualizzare [markdown](https://rich.readthedocs.io/en/latest/markdown.html) e tradurlo in modo da visualizzarlo su terminale. + +Per visualizzare markdown importa la classe `Markdown` e instanziala con una stringa contenente codice markdown. Dopo stampala sulla console. Ad esempio: + +```python +from rich.console import Console +from rich.markdown import Markdown + +console = Console() +with open("README.md") as readme: + markdown = Markdown(readme.read()) +console.print(markdown) +``` + +Questo produrrà un output simile al seguente: + +![markdown](https://github.com/willmcgugan/rich/raw/master/imgs/markdown.png) + +
+ +
+Evidenziazione della sintassi + +Rich utilizza la libreria [pygments](https://pygments.org/) per implementare il [syntax highlighting](https://rich.readthedocs.io/en/latest/syntax.html). L'utilizzo è simile a quello per visualizzare markdown; instanzia un oggetto `Syntax` e stampalo sulla console. Ad esempio: + +```python +from rich.console import Console +from rich.syntax import Syntax + +my_code = ''' +def iter_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]: + """Itera e genera una tupla con un flag per il primo e ultimo valore.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + first = True + for value in iter_values: + yield first, False, previous_value + first = False + previous_value = value + yield first, True, previous_value +''' +syntax = Syntax(my_code, "python", theme="monokai", line_numbers=True) +console = Console() +console.print(syntax) +``` + +Questo produrrà il seguente output: + +![syntax](https://github.com/willmcgugan/rich/raw/master/imgs/syntax.png) + +
+ +
+Tracebacks + +Rich può visualizzare [gradevoli tracebacks](https://rich.readthedocs.io/en/latest/traceback.html) che sono più semplici da leggere e che mostrano più codice rispetto ai Python tracebacks. Puoi impostare Rich come il traceback handler di default, in questo modo tutte le eccezioni non gestiti saranno visualizzate da Rich. + +Ecco come appare su OSX (simile a Linux): + +![traceback](https://github.com/willmcgugan/rich/raw/master/imgs/traceback.png) + +
+ +Tutti i Rich renderables utilizzano [Console Protocol](https://rich.readthedocs.io/en/latest/protocol.html), che puoi utilizzare per implementare nuovi contenuti su Rich. + +# Rich per le aziende + +Disponibile come parte dell'iscrizione a Tidelift. + +Lo sviluppatore di Rich e migliaia di altri packages lavorano con Tidelift per garantire supporto commerciale e mantenimento per i pacchetti open source che utilizzi per costruire le tue applicazioni. Risparmia tempo, riduci i rischi, e migliora la vita del codice, pagando i mantenitori dello stesso package che utilizzi. [Ulteriori informazioni.](https://tidelift.com/subscription/pkg/pypi-rich?utm_source=pypi-rich&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) + +# Progetti che usano Rich + +Ecco alcuni progetti che utilizzano Rich: + +- [BrancoLab/BrainRender](https://github.com/BrancoLab/BrainRender) + a python package for the visualization of three dimensional neuro-anatomical data +- [Ciphey/Ciphey](https://github.com/Ciphey/Ciphey) + Automated decryption tool +- [emeryberger/scalene](https://github.com/emeryberger/scalene) + a high-performance, high-precision CPU and memory profiler for Python +- [hedythedev/StarCli](https://github.com/hedythedev/starcli) + Browse GitHub trending projects from your command line +- [intel/cve-bin-tool](https://github.com/intel/cve-bin-tool) + This tool scans for a number of common, vulnerable components (openssl, libpng, libxml2, expat and a few others) to let you know if your system includes common libraries with known vulnerabilities. +- [nf-core/tools](https://github.com/nf-core/tools) + Python package with helper tools for the nf-core community. +- [cansarigol/pdbr](https://github.com/cansarigol/pdbr) + pdb + Rich library for enhanced debugging +- [plant99/felicette](https://github.com/plant99/felicette) + Satellite imagery for dummies. +- [seleniumbase/SeleniumBase](https://github.com/seleniumbase/SeleniumBase) + Automate & test 10x faster with Selenium & pytest. Batteries included. +- [smacke/ffsubsync](https://github.com/smacke/ffsubsync) + Automagically synchronize subtitles with video. +- [tryolabs/norfair](https://github.com/tryolabs/norfair) + Lightweight Python library for adding real-time 2D object tracking to any detector. +- [ansible/ansible-lint](https://github.com/ansible/ansible-lint) Ansible-lint checks playbooks for practices and behaviour that could potentially be improved +- [ansible-community/molecule](https://github.com/ansible-community/molecule) Ansible Molecule testing framework +- +[Many more](https://github.com/willmcgugan/rich/network/dependents)! + + diff --git a/README.ja.md b/README.ja.md index 56793fedbb..300f779d31 100644 --- a/README.ja.md +++ b/README.ja.md @@ -7,7 +7,8 @@ ![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) [English readme](https://github.com/willmcgugan/rich/blob/master/README.md) - • [中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) @@ -17,8 +18,10 @@ • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) -Richは、_リッチ_なテキストや美しい書式設定をターミナルで行うためのPythonライブラリです。 +Richは、 _リッチ_ なテキストや美しい書式設定をターミナルで行うためのPythonライブラリです。 [Rich API](https://rich.readthedocs.io/en/latest/)を使用すると、ターミナルの出力に色やスタイルを簡単に追加することができます。 Richはきれいなテーブル、プログレスバー、マークダウン、シンタックスハイライトされたソースコード、トレースバックなどをすぐに生成・表示することもできます。 @@ -36,15 +39,15 @@ Richは追加の設定を行わずとも、[Jupyter notebooks](https://jupyter.o ## インストール -`pip` や、あなたのお気に入りのPyPiパッケージマネージャを使ってインストールしてください。 +`pip` や、あなたのお気に入りのPyPIパッケージマネージャを使ってインストールしてください。 -``` -pip install rich +```sh +python -m pip install rich ``` 以下のコマンドを実行して、ターミナルでリッチの出力をテストできます: -``` +```sh python -m rich ``` diff --git a/README.kr.md b/README.kr.md index e1939becd1..b55529af72 100644 --- a/README.kr.md +++ b/README.kr.md @@ -7,7 +7,8 @@ ![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) [English readme](https://github.com/willmcgugan/rich/blob/master/README.md) - • [中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) @@ -17,6 +18,8 @@ • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) Rich는 터미널에서 _풍부한(rich)_ 텍스트와 아름다운 서식을 지원하기 위한 파이썬 라이브러리입니다. @@ -36,15 +39,15 @@ Rich는 [Jupyter notebooks](https://jupyter.org/)에서 별도의 설정없이 ## 설치 -`pip` 또는 좋아하는 Pypi 패키지 매니저로 설치하세요. +`pip` 또는 좋아하는 PyPI 패키지 매니저로 설치하세요. -``` -pip install rich +```sh +python -m pip install rich ``` 아래 명령어를 통해 터미널에서 Rich 출력을 테스트해보세요. -``` +```sh python -m rich ``` diff --git a/README.md b/README.md index ae8cd10264..f7cf6368fa 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ ![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) [English readme](https://github.com/willmcgugan/rich/blob/master/README.md) - • [中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) @@ -18,6 +19,8 @@ • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) Rich is a Python library for _rich_ text and beautiful formatting in the terminal. @@ -37,15 +40,15 @@ Rich works with [Jupyter notebooks](https://jupyter.org/) with no additional con ## Installing -Install with `pip` or your favorite PyPi package manager. +Install with `pip` or your favorite PyPI package manager. -``` -pip install rich +```sh +python -m pip install rich ``` Run the following to test Rich output on your terminal: -``` +```sh python -m rich ``` diff --git a/README.pt-br.md b/README.pt-br.md index 504052efa7..55f366cafd 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -7,7 +7,8 @@ ![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) [English readme](https://github.com/willmcgugan/rich/blob/master/README.md) - • [中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) @@ -17,34 +18,35 @@ • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) -Rich é uma biblioteca Python para _rich_ text e formatação de estilos no termial. +Rich é uma biblioteca Python para _rich_ text e formatação de estilos no terminal. -A [API do Rich](https://rich.readthedocs.io/en/latest/) permite adicionar cores e estilos no output do termial de forma fácil. Rich também permite formataçao de tabelas, barra de progresso, markdown, highlight de sintaxe de código fonte, rastreio de erros (traceback), e muito mais. +A [API do Rich](https://rich.readthedocs.io/en/latest/) permite adicionar cores e estilos no output do terminal de forma fácil. Rich também permite formataçao de tabelas, barra de progresso, markdown, highlight de sintaxe de código fonte, rastreio de erros (traceback) e muito mais. ![Funcões](https://github.com/willmcgugan/rich/raw/master/imgs/features.png) -Para mais detalhes, veja um vídeo de introdução so Rick em [calmcode.io](https://calmcode.io/rich/introduction.html) por [@fishnets88](https://twitter.com/fishnets88). +Para mais detalhes, veja um vídeo de introdução so Rich em [calmcode.io](https://calmcode.io/rich/introduction.html) por [@fishnets88](https://twitter.com/fishnets88). Veja aqui [o que estão falando sobre o Rich](https://www.willmcgugan.com/blog/pages/post/rich-tweets/). ## Compatibilidade -Rich funciona no Linux, OSX e Windows. True color / emoji funciona no novo Terminal do Windows, o terminal classico é limitado a 16 cores. Rich requer Python 3.6.1 or later. +Rich funciona no Linux, OSX e Windows. True color / emoji funciona no novo Terminal do Windows, o terminal classico é limitado a 16 cores. Rich requer Python 3.6.1 ou superior. Rich funciona com [Jupyter notebooks](https://jupyter.org/) sem a necessidade de configurações adicionais. ## Instalação -Instale usando `pip` ou seu gerenciador de pacotes PyPi favorito. +Instale usando `pip` ou seu gerenciador de pacotes PyPI favorito. -``` -pip install rich +```sh +python -m pip install rich ``` Execute o seguinte comando para testar o output do Rich no seu terminal: -``` +```sh python -m rich ``` @@ -111,7 +113,7 @@ Voce pode usar o objeto do Console para gerar facilmente uma saída para o termi ## Inspect do Rich -O Rich tem uma função [inspect](https://rich.readthedocs.io/en/latest/reference/init.html?highlight=inspect#rich.inspect) que gera um relatório de qualquer objeto no Python, como classes, instancias ou funções nativas. +O Rich tem uma função [inspect](https://rich.readthedocs.io/en/latest/reference/init.html?highlight=inspect#rich.inspect) que gera um relatório de qualquer objeto no Python, como classes, instâncias ou funções nativas. ```python >>> my_list = ["foo", "bar"] @@ -125,7 +127,7 @@ Confira a [documentação do inspect](https://rich.readthedocs.io/en/latest/refe # A biblioteca Rich -O Rich possui vários _renderizaveis_ nativos que podem ser usados para criar outputs elegantes no seu CLI e ajudar a debugar o código. +O Rich possui vários _renderizáveis_ nativos que podem ser usados para criar outputs elegantes no seu CLI e ajudar a debugar o código. Clique nos itens a seguir para expandir os detalhes: @@ -185,18 +187,18 @@ Para imprimir um emoji no console, coloque o nome do emoji entre dois ":" (dois 😃 🧛 💩 👍 🦝 ``` -Please use this feature wisely. +Por favor use esse recurso com sabedoria.
Tabelas -O Rich pode imprimir [tables](https://rich.readthedocs.io/en/latest/tables.html) flexiveis usando caracteres unicode como bordas. Existem várias opções de formatação de bordas, estilos, alinhamento das celulas etc. +O Rich pode imprimir [tables](https://rich.readthedocs.io/en/latest/tables.html) flexíveis usando caracteres unicode como bordas. Existem várias opções de formatação de bordas, estilos, alinhamento das celulas, etc. ![table movie](https://github.com/willmcgugan/rich/raw/master/imgs/table_movie.gif) -A animação acima foi gerada com o arquivo [table_movie.py](https://github.com/willmcgugan/rich/blob/master/examples/table_movie.py) da pasta de exeplos. +A animação acima foi gerada com o arquivo [table_movie.py](https://github.com/willmcgugan/rich/blob/master/examples/table_movie.py) da pasta de exemplos. Veja um exemplo mais simple: @@ -236,7 +238,7 @@ Que gera o seguinte resultado: Observe que o markup é renderizado da mesma for que em `print()` e `log()`. De fato, tudo que é renderizável pelo Rich pode ser incluído nos cabeçalhos ou linhas (até mesmo outras tabelas). -A class `Table` é inteligente o suficiente para ajustar o tamanho das colunas para caber na largura do terminal, quebrando o texto em novas linhas como necessário. Veja a seguir o mesmo exemplo, só que desta vez com um terminal menor do que o tamanho original da tabela: +A classe `Table` é inteligente o suficiente para ajustar o tamanho das colunas para caber na largura do terminal, quebrando o texto em novas linhas quando necessário. Veja a seguir o mesmo exemplo, só que desta vez com um terminal menor do que o tamanho original da tabela: ![table2](https://github.com/willmcgugan/rich/raw/master/imgs/table2.png) @@ -256,7 +258,6 @@ for step in track(range(100)): do_step(step) ``` -It's not much harder to add multiple progress bars. Here's an example taken from the docs: Adicionar multiplas barras de progresso também é simples. Veja outro exemplo que existe na documentação: ![progress](https://github.com/willmcgugan/rich/raw/master/imgs/progress.gif) @@ -265,7 +266,7 @@ As colunas podem ser configuradas pra mostrar qualquer detalho necessário. As c ![progress](https://github.com/willmcgugan/rich/raw/master/imgs/downloader.gif) -Para testar isso no seu terminal, use o arquivo [examples/downloader.py](https://github.com/willmcgugan/rich/blob/master/examples/downloader.py) para fazer o download de multiplas URLs simultaneamente, exibindo o progress de cada download. +Para testar isso no seu terminal, use o arquivo [examples/downloader.py](https://github.com/willmcgugan/rich/blob/master/examples/downloader.py) para fazer o download de multiplas URLs simultaneamente, exibindo o progresso de cada download.
@@ -319,7 +320,6 @@ Isso gera o seguinte resultado: ![markdown](https://github.com/willmcgugan/rich/raw/master/imgs/tree.png) -Veja o exemplo em [tree.py](https://github.com/willmcgugan/rich/blob/master/examples/tree.py) de um script that displays a tree view of any directory, similar to the linux `tree` command. Veja o exemplo em [tree.py](https://github.com/willmcgugan/rich/blob/master/examples/tree.py) de um código que gera uma árvore de exibição de um dicionário, semelhante ao comando `tree` do linux. @@ -340,7 +340,6 @@ directory = os.listdir(sys.argv[1]) print(Columns(directory)) ``` -The following screenshot is the output from the [columns example](https://github.com/willmcgugan/rich/blob/master/examples/columns.py) which displays data pulled from an API in columns: O screenshot a seguir é do resultado do [exemplo de colunas](https://github.com/willmcgugan/rich/blob/master/examples/columns.py) formatando em colunas os dados extraidos de uma API: ![columns](https://github.com/willmcgugan/rich/raw/master/imgs/columns.png) @@ -444,14 +443,14 @@ Aqui estão alguns projetos que usam o Rich: - [cansarigol/pdbr](https://github.com/cansarigol/pdbr) pdb + Rich para auxiliar no debug - [plant99/felicette](https://github.com/plant99/felicette) - Imagem de satélites para tolos. + Imagem de satélites para iniciantes. - [seleniumbase/SeleniumBase](https://github.com/seleniumbase/SeleniumBase) Automatize & teste 10x mais rápido com Selenium & pytest. Baterias inclusas. - [smacke/ffsubsync](https://github.com/smacke/ffsubsync) Automagicamente sincronize legendas com vídeos. - [tryolabs/norfair](https://github.com/tryolabs/norfair) Biblioteca Python para adicionar rastreio em tempo real de objetos 2D em qualquer detector. -- [ansible/ansible-lint](https://github.com/ansible/ansible-lint) Ansible-lint verifica boas praticas e comportamento que podem ser melhorados. +- [ansible/ansible-lint](https://github.com/ansible/ansible-lint) Ansible-lint verifica boas práticas e comportamento que podem ser melhorados. - [ansible-community/molecule](https://github.com/ansible-community/molecule) Framework de test para Ansible Molecule - +[Muitos outros](https://github.com/willmcgugan/rich/network/dependents)! diff --git a/README.ru.md b/README.ru.md new file mode 100644 index 0000000000..b6ff2d255e --- /dev/null +++ b/README.ru.md @@ -0,0 +1,458 @@ +[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich/10.11.0)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich) + +[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich) +[![codecov](https://codecov.io/gh/willmcgugan/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/willmcgugan/rich) +[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/) +[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan) + +![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) + +[English readme](https://github.com/willmcgugan/rich/blob/master/README.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) + • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) + • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) + • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) + • [日本語 readme](https://github.com/willmcgugan/rich/blob/master/README.ja.md) + • [한국어 readme](https://github.com/willmcgugan/rich/blob/master/README.kr.md) + • [Français readme](https://github.com/willmcgugan/rich/blob/master/README.fr.md) + • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) + • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) + • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) + +Rich это Python библиотека позволяющая отображать _красивый_ текст и форматировать терминал. + +[Rich API](https://rich.readthedocs.io/en/latest/) упрощает добавление цветов и стилей к выводу терминала. Rich также позволяет отображать красивые таблицы, прогресс бары, markdown, код с отображением синтаксиса, ошибки, и т.д. — прямо после установки. + +![Features](https://github.com/willmcgugan/rich/raw/master/imgs/features.png) + +Для видео инструкции смотрите [calmcode.io](https://calmcode.io/rich/introduction.html) от [@fishnets88](https://twitter.com/fishnets88). + +Посмотрите [что люди думают о Rich](https://www.willmcgugan.com/blog/pages/post/rich-tweets/). + +## Cовместимость + +Rich работает с Linux, OSX, и Windows. True color / эмоджи работают с новым терминалом Windows, классический терминал лимитирован 16 цветами. Rich требует Python 3.6.1 или более новый. + +Rich работает с [Jupyter notebooks](https://jupyter.org/) без дополнительной конфигурации. + +## Установка + +Установите с `pip` или вашим любимым PyPI менеджером пакетов. + +```sh +python -m pip install rich +``` + +Запустите следующею команду чтобы проверить Rich вывод в вашем терминале: + +```sh +python -m rich +``` + +## Rich Print + +Простейший способ получить красивый вывод это импортировать метод [rich print](https://rich.readthedocs.io/en/latest/introduction.html#quick-start), он принимает такие же аргументы что и стандартный метод print. Попробуйте: + +```python +from rich import print + +print("Hello, [bold magenta]World[/bold magenta]!", ":vampire:", locals()) +``` + +![Hello World](https://github.com/willmcgugan/rich/raw/master/imgs/print.png) + +## Rich REPL + +Rich может быть установлен в Python REPL, так, все данные будут выведены через Rich. + +```python +>>> from rich import pretty +>>> pretty.install() +``` + +![REPL](https://github.com/willmcgugan/rich/raw/master/imgs/repl.png) + +## Использование класса Console + +Для большего контроля над терминалом Rich, импортируйте и инициализируйте класс [Console](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console). + +```python +from rich.console import Console + +console = Console() +``` + +У класса console есть метод `print` который имеет идентичный функционал к встроеной функции `print`. Вот пример использования: + +```python +console.print("Hello", "World!") +``` + +Как вы могли подумать, этот выведет `"Hello World!"` в терминал. Запомните что, в отличии от встроеной функции `print`, Rich увеличит ваш текст так, чтобы он распространялся на всю ширину терминала. + +Есть несколько способов добавить цвет и стиль к вашему выводу. Вы можете выбрать стиль для всего вывода добавив аргумент `style`. Вот пример: + +```python +console.print("Hello", "World!", style="bold red") +``` + +Вывод будет выглядить примерно так: + +![Hello World](https://github.com/willmcgugan/rich/raw/master/imgs/hello_world.png) + +Этого достаточно чтобы стилизовать 1 строку. Для более детального стилизования, Rich использует специальную разметку похожую по синтаксису на [bbcode](https://en.wikipedia.org/wiki/BBCode). Вот пример: + +```python +console.print("Where there is a [bold cyan]Will[/bold cyan] there [u]is[/u] a [i]way[/i].") +``` + +![Console Markup](https://github.com/willmcgugan/rich/raw/master/imgs/where_there_is_a_will.png) + +Вы можете использовать класс Console чтобы генерировать утонченный вывод с минимальными усилиями. Смотрите [документацию Console API](https://rich.readthedocs.io/en/latest/console.html) для детального объяснения. + +## Rich Inspect + +В Rich есть функция [inspect](https://rich.readthedocs.io/en/latest/reference/init.html?highlight=inspect#rich.inspect) которая может украсить любой Python объект, например класс, переменная, или функция. + +```python +>>> my_list = ["foo", "bar"] +>>> from rich import inspect +>>> inspect(my_list, methods=True) +``` + +![Log](https://github.com/willmcgugan/rich/raw/master/imgs/inspect.png) + +Смотрите [документацию inspect](https://rich.readthedocs.io/en/latest/reference/init.html#rich.inspect) для детального объяснения. + +# Библиотека Rich + +Rich содержит несколько встроенных _визуализаций_ которые вы можете использовать чтобы сделать элегантный вывод в важем CLI или помочь в дебаггинге кода. + +Вот несколько вещей которые может делать Rich (нажмите чтобы узнать больше): + +
+Лог + +В классе console есть метод `log()` который похож на `print()`, но также изображает столбец для текущего времени, файла и линии кода которая вызвала метод. По умолчанию Rich будет подсвечивать синтаксис для структур Python и для строк repr. Если вы передадите в метод коллекцию (т.е. dict или list) Rich выведет её так, чтобы она помещалась в доступном месте. Вот пример использования этого метода. + +```python +from rich.console import Console +console = Console() + +test_data = [ + {"jsonrpc": "2.0", "method": "sum", "params": [None, 1, 2, 4, False, True], "id": "1",}, + {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}, + {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "2"}, +] + +def test_log(): + enabled = False + context = { + "foo": "bar", + } + movies = ["Deadpool", "Rise of the Skywalker"] + console.log("Hello from", console, "!") + console.log(test_data, log_locals=True) + + +test_log() +``` + +Код выше выведет это: + +![Log](https://github.com/willmcgugan/rich/raw/master/imgs/log.png) + +Запомните аргумент `log_locals`, он выводит таблицу имеющую локальные переменные функции в которой метод был вызван. + +Метод может быть использован для вывода данных в терминал в длинно-работающих программ, таких как сервера, но он также может помочь в дебаггинге. + +
+
+Обработчик Логов + +Вы также можете использовать встроенный [класс Handler](https://rich.readthedocs.io/en/latest/logging.html) чтобы форматировать и раскрашивать вывод из встроенной библиотеки logging. Вот пример вывода: + +![Logging](https://github.com/willmcgugan/rich/raw/master/imgs/logging.png) + +
+ +
+Эмоджи + +Чтобы вставить эмоджи в вывод консоли поместите название между двумя двоеточиями. Вот пример: + +```python +>>> console.print(":smiley: :vampire: :pile_of_poo: :thumbs_up: :raccoon:") +😃 🧛 💩 👍 🦝 +``` + +Пожалуйста, используйте это мудро. + +
+ +
+Таблицы + +Rich может отображать гибкие [таблицы](https://rich.readthedocs.io/en/latest/tables.html) с символами unicode. Есть большое количество форматов границ, стилей, выравниваний ячеек и т.п. + +![table movie](https://github.com/willmcgugan/rich/raw/master/imgs/table_movie.gif) + +Эта анимация была сгенерирована с помощью [table_movie.py](https://github.com/willmcgugan/rich/blob/master/examples/table_movie.py) в директории примеров. + +Вот пример более простой таблицы: + +```python +from rich.console import Console +from rich.table import Table + +console = Console() + +table = Table(show_header=True, header_style="bold magenta") +table.add_column("Date", style="dim", width=12) +table.add_column("Title") +table.add_column("Production Budget", justify="right") +table.add_column("Box Office", justify="right") +table.add_row( + "Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$275,000,000", "$375,126,118" +) +table.add_row( + "May 25, 2018", + "[red]Solo[/red]: A Star Wars Story", + "$275,000,000", + "$393,151,347", +) +table.add_row( + "Dec 15, 2017", + "Star Wars Ep. VIII: The Last Jedi", + "$262,000,000", + "[bold]$1,332,539,889[/bold]", +) + +console.print(table) +``` + +Этот пример выводит: + +![table](https://github.com/willmcgugan/rich/raw/master/imgs/table.png) + +Запомните что разметка консоли отображается таким же способом что и `print()` и `log()`. На самом деле, всё, что может отобразить Rich может быть в заголовках или рядах (даже другие таблицы). + +Класс `Table` достаточно умный чтобы менять размер столбцов, так, чтобы они заполняли доступную ширину терминала, обёртывая текст как нужно. Вот тот же самый пример с терминалом меньше таблицы: + +![table2](https://github.com/willmcgugan/rich/raw/master/imgs/table2.png) + +
+ +
+Прогресс Бары + +Rich может отображать несколько плавных [прогресс](https://rich.readthedocs.io/en/latest/progress.html) баров чтобы отслеживать долго-идущие задания. + +Для базового использования, оберните любую последовательность в функции `track` и переберите результат. Вот пример: + +```python +from rich.progress import track + +for step in track(range(100)): + do_step(step) +``` + +Отслеживать больше чем 1 задание не сложнее. Вот пример взятый из документации: + +![progress](https://github.com/willmcgugan/rich/raw/master/imgs/progress.gif) + +Столбцы могут быть настроены чтобы показывать любые детали. Стандартные столбцы содержат проценты исполнения, размер файлы, скорость файла, и оставшееся время. Вот ещё пример показывающий загрузку в прогрессе: + +![progress](https://github.com/willmcgugan/rich/raw/master/imgs/downloader.gif) + +Чтобы попробовать самому, скачайте [examples/downloader.py](https://github.com/willmcgugan/rich/blob/master/examples/downloader.py) который может скачать несколько URL одновременно пока отображая прогресс. + +
+ +
+Статус + +Для ситуаций где сложно высчитать прогресс, вы можете использовать метод [статус](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console.status) который будет отображать крутящуюся анимацию и сообщение. Анимация не перекроет вам доступ к консоли. Вот пример: + +```python +from time import sleep +from rich.console import Console + +console = Console() +tasks = [f"task {n}" for n in range(1, 11)] + +with console.status("[bold green]Working on tasks...") as status: + while tasks: + task = tasks.pop(0) + sleep(1) + console.log(f"{task} complete") +``` + +Это генерирует вот такой вывод в консоль. + +![status](https://github.com/willmcgugan/rich/raw/master/imgs/status.gif) + +Крутящиеся анимации были взяты из [cli-spinners](https://www.npmjs.com/package/cli-spinners). Вы можете выбрать одну из них указав параметр `spinner`. Запустите следующую команду чтобы узнать доступные анимации: + +``` +python -m rich.spinner +``` + +Эта команда выдаёт вот такой вывод в терминал: + +![spinners](https://github.com/willmcgugan/rich/raw/master/imgs/spinners.gif) + +
+ +
+Дерево + +Rich может отобразить [дерево](https://rich.readthedocs.io/en/latest/tree.html) с указаниями. Дерево идеально подходит для отображения структуры файлов или любых других иерархических данных. + +Ярлыки дерева могут быть простым текстом или любой другой вещью Rich может отобразить. Запустите следующую команду для демонстрации: + +``` +python -m rich.tree +``` + +Это генерирует следующий вывод: + +![markdown](https://github.com/willmcgugan/rich/raw/master/imgs/tree.png) + +Смотрите пример [tree.py](https://github.com/willmcgugan/rich/blob/master/examples/tree.py) для скрипта который отображает дерево любой директории, похоже на команду linux `tree`. + +
+ +
+Столбцы + +Rich может отображать контент в [столбцах](https://rich.readthedocs.io/en/latest/columns.html) с равной или оптимальной шириной. Вот очень простой пример клона команды `ls` (MacOS / Linux) который отображает a файлы директории в столбцах: + +```python +import os +import sys + +from rich import print +from rich.columns import Columns + +directory = os.listdir(sys.argv[1]) +print(Columns(directory)) +``` + +Следующий скриншот это вывод из [примера столбцов](https://github.com/willmcgugan/rich/blob/master/examples/columns.py) который изображает данные взятые из API в столбцах: + +![columns](https://github.com/willmcgugan/rich/raw/master/imgs/columns.png) + +
+ +
+Markdown + +Rich может отображать [markdown](https://rich.readthedocs.io/en/latest/markdown.html) и делает неплохую работу в форматировании под терминал. + +Чтобы отобразить markdown импортируйте класс `Markdown` и инициализируйте его с помощью строки содержащей код markdown. После чего выведите его в консоль. Вот пример: + +```python +from rich.console import Console +from rich.markdown import Markdown + +console = Console() +with open("README.md") as readme: + markdown = Markdown(readme.read()) +console.print(markdown) +``` + +Это выведет что-то похожее на это: + +![markdown](https://github.com/willmcgugan/rich/raw/master/imgs/markdown.png) + +
+ +
+Подсвечивание Синтаксиса + +Rich использует библиотеку [pygments](https://pygments.org/) чтобы имплементировать [подсвечивание синтаксиса](https://rich.readthedocs.io/en/latest/syntax.html). Использование похоже на отображение markdown; инициализируйте класс `Syntax` и выводите его в консоль. Вот пример: + +```python +from rich.console import Console +from rich.syntax import Syntax + +my_code = ''' +def iter_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]: + """Iterate and generate a tuple with a flag for first and last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + first = True + for value in iter_values: + yield first, False, previous_value + first = False + previous_value = value + yield first, True, previous_value +''' +syntax = Syntax(my_code, "python", theme="monokai", line_numbers=True) +console = Console() +console.print(syntax) +``` + +Это выведет что-то похожее на это: + +![syntax](https://github.com/willmcgugan/rich/raw/master/imgs/syntax.png) + +
+ +
+Ошибки + +Rich может отображать [красивые ошибки](https://rich.readthedocs.io/en/latest/traceback.html) которые проще читать и показывают больше кода чем стандартные ошибки Python. Вы можете установить Rich как стандартный обработчик ошибок чтобы все непойманные ошибки отображал Rich. + +Вот как это выглядит на OSX (похоже на Linux): + +![traceback](https://github.com/willmcgugan/rich/raw/master/imgs/traceback.png) + +
+ +Все визуализации Rich используют [протокол Console](https://rich.readthedocs.io/en/latest/protocol.html), который также позволяет вам добавлять свой Rich контент. + +# Rich для предприятий + +Rich доступен как часть подписки Tidelift. + +Поддержатели проекта Rich и тысячи других работают над подпиской Tidelift чтобы предоставить коммерческую поддержку и поддержание для проектов с открытым кодом вы используете чтобы построить своё приложение. Сохраните время, избавьтесь от риска, и улучшите состояние кода, пока вы платите поддержателям проектов вы используете. [Узнайте больше.](https://tidelift.com/subscription/pkg/pypi-rich?utm_source=pypi-rich&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) + +# Проекты использующие Rich + +Вот пару проектов использующих Rich: + +- [BrancoLab/BrainRender](https://github.com/BrancoLab/BrainRender) + библиотека Python для визуализации нейроанатомических данных в 3 измерениях +- [Ciphey/Ciphey](https://github.com/Ciphey/Ciphey) + автоматизированная утилита для расшифровки +- [emeryberger/scalene](https://github.com/emeryberger/scalene) + Высокая производительность, высокая точность CPU и профилировщик памяти для Python +- [hedythedev/StarCli](https://github.com/hedythedev/starcli) + Просматривайте трендовые проекты GitHub прямо из вашего терминала +- [intel/cve-bin-tool](https://github.com/intel/cve-bin-tool) + Эта утилита сканирует известные уязвимости (openssl, libpng, libxml2, expat and a few others) чтобы уведомить вас если ваша система использует библиотеки с известными уязвимостями. +- [nf-core/tools](https://github.com/nf-core/tools) + Библиотека Python с полезными инструментами для сообщества nf-core. +- [cansarigol/pdbr](https://github.com/cansarigol/pdbr) + pdb + Rich библиотека для улучшенного дебаггинга +- [plant99/felicette](https://github.com/plant99/felicette) + Изображения со спутников для чайников. +- [seleniumbase/SeleniumBase](https://github.com/seleniumbase/SeleniumBase) + Автоматизируйте и тестируйте в 10 раз быстрее с Selenium и pytest. Батарейки включены. +- [smacke/ffsubsync](https://github.com/smacke/ffsubsync) + Автоматически синхронизируйте субтитры с видео. +- [tryolabs/norfair](https://github.com/tryolabs/norfair) + Простая библиотека Python для добавления 2D отслеживания к любому детектеру в реальном времени. +- [ansible/ansible-lint](https://github.com/ansible/ansible-lint) Ansible-lint проверяет пьесы для практик и поведений которые могут быть исправлены +- [ansible-community/molecule](https://github.com/ansible-community/molecule) Ansible Molecule тестинг фреймворк +- +[Ещё больше](https://github.com/willmcgugan/rich/network/dependents)! + + diff --git a/README.sv.md b/README.sv.md index d6cf99681f..b2685a705e 100644 --- a/README.sv.md +++ b/README.sv.md @@ -7,7 +7,8 @@ ![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) [English readme](https://github.com/willmcgugan/rich/blob/master/README.md) - • [中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) @@ -17,6 +18,7 @@ • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) Rich är ett Python bibliotek för _rich_ text och vacker formattering i terminalen. @@ -36,15 +38,15 @@ Rich funkar med [Jupyter notebooks](https://jupyter.org/) utan någon ytterligar ## Installering -Installera med `pip` eller din favorita PyPi packet hanterare. +Installera med `pip` eller din favorita PyPI packet hanterare. -``` -pip install rich +```sh +python -m pip install rich ``` Kör följade följande för att testa Rich utmatning i din terminal: -``` +```sh python -m rich ``` diff --git a/README.zh-tw.md b/README.zh-tw.md new file mode 100644 index 0000000000..207f3260f7 --- /dev/null +++ b/README.zh-tw.md @@ -0,0 +1,457 @@ +[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich) +[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich) +[![codecov](https://codecov.io/gh/willmcgugan/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/willmcgugan/rich) +[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/) +[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan) + +![Logo](https://github.com/willmcgugan/rich/raw/master/imgs/logo.svg) + +[English readme](https://github.com/willmcgugan/rich/blob/master/README.md) + • [简体中文 readme](https://github.com/willmcgugan/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/willmcgugan/rich/blob/master/README.zh-tw.md) + • [Lengua española readme](https://github.com/willmcgugan/rich/blob/master/README.es.md) + • [Deutsche readme](https://github.com/willmcgugan/rich/blob/master/README.de.md) + • [Läs på svenska](https://github.com/willmcgugan/rich/blob/master/README.sv.md) + • [日本語 readme](https://github.com/willmcgugan/rich/blob/master/README.ja.md) + • [한국어 readme](https://github.com/willmcgugan/rich/blob/master/README.kr.md) + • [Français readme](https://github.com/willmcgugan/rich/blob/master/README.fr.md) + • [Schwizerdütsch readme](https://github.com/willmcgugan/rich/blob/master/README.de-ch.md) + • [हिन्दी readme](https://github.com/willmcgugan/rich/blob/master/README.hi.md) + • [Português brasileiro readme](https://github.com/willmcgugan/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/willmcgugan/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/willmcgugan/rich/blob/master/README.ru.md) + +Rich 是一款提供終端機介面中 _豐富的_ 文字效果及精美的格式設定的 Python 函式庫。 + +[Rich API](https://rich.readthedocs.io/en/latest/) 讓終端機介面加上色彩及樣式變得易如反掌。Rich 也可以繪製漂亮的表格、進度條、Markdown、語法醒目標示的程式碼、Traceback(追溯)……。 + +![Features](https://github.com/willmcgugan/rich/raw/master/imgs/features.png) + +關於 Rich 的介紹,請參見 [@fishnets88](https://twitter.com/fishnets88) 在 [calmcode.io](https://calmcode.io/rich/introduction.html) 錄製的影片。 + +[看看其他人對於 Rich 的討論](https://www.willmcgugan.com/blog/pages/post/rich-tweets/)。 + +## 相容性 + +Rich 可在 Linux、macOS、Windows 上運作。在新的 Windows Terminal 中可支援顯示全彩及 Emoji,但傳統的終端機中僅支援 16 色。Rich 最低需要的 Python 版本為 3.6.1。 + +Rich 可在 [Jupyter notebooks](https://jupyter.org/) 上使用,無須額外設定。 + +## 安裝 + +以 `pip` 或 PyPI 套件管理器安裝。 + +```sh +python -m pip install rich +``` + +以此命令測試 Rich 在終端機的輸出效果: + +```sh +python -m rich +``` + +## Rich Print + +匯入 [rich print](https://rich.readthedocs.io/en/latest/introduction.html#quick-start) 方法就可以輕鬆地讓程式進行 rich 輸出,rich print 與 Python 內建的函式用法相似。試試: + +```python +from rich import print + +print("Hello, [bold magenta]World[/bold magenta]!", ":vampire:", locals()) +``` + +![Hello World](https://github.com/willmcgugan/rich/raw/master/imgs/print.png) + +## Rich REPL + +Rich 可以安裝在 Python REPL 中,如此一來就可以漂亮的輸出與突顯標示任何資料結構。 + +```python +>>> from rich import pretty +>>> pretty.install() +``` + +![REPL](https://github.com/willmcgugan/rich/raw/master/imgs/repl.png) + +## 使用 Console + +匯入並建構 [Console](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console) 物件,以更全面地控制 rich 終端機內容。 + +```python +from rich.console import Console + +console = Console() +``` + +Console 物件有個 `print` 方法,且刻意設計的與內建 `print` 函式相似。參考此範例: + +```python +console.print("Hello", "World!") +``` + +如同預期的,這會將 `"Hello World!"` 印在終端機。須注意不同於內建的 `print` 函式,Rich 會自動將過長的文字換行,以符合終端機的寬度。 + +有幾種加上顏色及樣式的方式。您可以用 `style` 引數設定輸出內容的樣式,參考此範例: + +```python +console.print("Hello", "World!", style="bold red") +``` + +輸出結果如下圖: + +![Hello World](https://github.com/willmcgugan/rich/raw/master/imgs/hello_world.png) + +介紹完了如何對整行文字設定樣式,接著來看看更細部的使用。Rich 可以接受類似 [bbcode](https://en.wikipedia.org/wiki/BBCode) 的語法,對個別文字設定樣式。參考此範例: + +```python +console.print("Where there is a [bold cyan]Will[/bold cyan] there [u]is[/u] a [i]way[/i].") +``` + +![Console Markup](https://github.com/willmcgugan/rich/raw/master/imgs/where_there_is_a_will.png) + +您可以用 Console 物件不費吹灰之力地達成細膩的輸出效果。參閱 [Console API](https://rich.readthedocs.io/en/latest/console.html) 說明文件以了解細節。 + +## Rich Inspect + +Rich 提供了 [inspect](https://rich.readthedocs.io/en/latest/reference/init.html?highlight=inspect#rich.inspect) 函式,可以對任何 Python 物件,如 class、instance 或 builtin ,為其產生一份報告。 + +```python +>>> my_list = ["foo", "bar"] +>>> from rich import inspect +>>> inspect(my_list, methods=True) +``` + +![Log](https://github.com/willmcgugan/rich/raw/master/imgs/inspect.png) + +參閱 [inspect 說明文件](https://rich.readthedocs.io/en/latest/reference/init.html#rich.inspect) 以了解細節。 + +# Rich 函式庫 + +Rich 包含了一系列可繪製的物件,您可以使用它們來印出精美的畫面,或者協助偵錯程式碼。 + +按一下子標題以了解細節: + +
+Log + +Console 物件提供了 `log()` 方法,使用方式與 `print()` 類似,但還多了一欄來顯示目前時間、進行呼叫的檔案及行號。預設情況下 Rich 會語法醒目標示 Python 的結構及 repr 字串。若使用於字典或串列這類集合性物件,Rich 會將其漂亮地印出來,以符合可用空間。此範例示範了這些功能。 + +```python +from rich.console import Console +console = Console() + +test_data = [ + {"jsonrpc": "2.0", "method": "sum", "params": [None, 1, 2, 4, False, True], "id": "1",}, + {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}, + {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "2"}, +] + +def test_log(): + enabled = False + context = { + "foo": "bar", + } + movies = ["Deadpool", "Rise of the Skywalker"] + console.log("Hello from", console, "!") + console.log(test_data, log_locals=True) + + +test_log() +``` + +上面的程式碼會產生下圖結果: + +![Log](https://github.com/willmcgugan/rich/raw/master/imgs/log.png) + +注意到 `log_locals` 引數,可用來輸出一張表格,用來顯示 log 方法被呼叫時,區域變數的內容。 + +log 方法可用於伺服器上長時間運作的程式,也很適合一般程式偵錯用途。 + +
+
+Logging Handler + +您也可以使用內建的 [Handler 類別](https://rich.readthedocs.io/en/latest/logging.html) 來將 Python logging 模組的輸出內容格式化並賦予色彩: + +![Logging](https://github.com/willmcgugan/rich/raw/master/imgs/logging.png) + +
+ +
+Emoji + +以一對冒號包住表情符號的名稱,來透過 console 插入 Emoji。參考範例: + +```python +>>> console.print(":smiley: :vampire: :pile_of_poo: :thumbs_up: :raccoon:") +😃 🧛 💩 👍 🦝 +``` + +請謹慎使用此功能。 + +
+ +
+表格 + +Rich 可以用 unicode box 字元繪製彈性的 [表格](https://rich.readthedocs.io/en/latest/tables.html)。格式設定十分多元,包含框線、樣式、儲存格對齊……。 + +![table movie](https://github.com/willmcgugan/rich/raw/master/imgs/table_movie.gif) + +上圖的動畫效果是以 [table_movie.py](https://github.com/willmcgugan/rich/blob/master/examples/table_movie.py) 產生的,該檔案位於 examples 資料夾。 + +參考這個簡單的表格範例: + +```python +from rich.console import Console +from rich.table import Table + +console = Console() + +table = Table(show_header=True, header_style="bold magenta") +table.add_column("Date", style="dim", width=12) +table.add_column("Title") +table.add_column("Production Budget", justify="right") +table.add_column("Box Office", justify="right") +table.add_row( + "Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$275,000,000", "$375,126,118" +) +table.add_row( + "May 25, 2018", + "[red]Solo[/red]: A Star Wars Story", + "$275,000,000", + "$393,151,347", +) +table.add_row( + "Dec 15, 2017", + "Star Wars Ep. VIII: The Last Jedi", + "$262,000,000", + "[bold]$1,332,539,889[/bold]", +) + +console.print(table) +``` + +執行結果如圖: + +![table](https://github.com/willmcgugan/rich/raw/master/imgs/table.png) + +請留意,主控台標記的呈現方式與 `print()`、`log()` 相同。事實上,由 Rich 繪製的任何東西都可以被放在任何標題、列,甚至其他表格裡。 + +`Table` 類別很聰明,能夠自動調整欄寬來配合終端機的大小,也會在需要時自動將文字換行。此範例的程式碼與上一個相同,然而終端機變小了一點: + +![table2](https://github.com/willmcgugan/rich/raw/master/imgs/table2.png) + +
+ +
+進度條 + +Rich 可繪製多個不閃爍的 [進度條](https://rich.readthedocs.io/en/latest/progress.html),以追蹤需時較久的工作。 + +基本的使用方式,是將序列放在 `track` 函式中,再對其結果疊代。參考此範例: + +```python +from rich.progress import track + +for step in track(range(100)): + do_step(step) +``` + +新增多個進度條也不是難事,來看看說明文件中的範例: + +![progress](https://github.com/willmcgugan/rich/raw/master/imgs/progress.gif) + +您可以調整要顯示的狀態欄位。內建的欄位包含完成百分比、檔案大小、讀寫速度及剩餘時間。來看看另一個用來顯示下載進度的範例: + +![progress](https://github.com/willmcgugan/rich/raw/master/imgs/downloader.gif) + +想嘗試看看嗎?您可以在 [examples/downloader.py](https://github.com/willmcgugan/rich/blob/master/examples/downloader.py) 取得此範例程式。此程式可以在下載多個檔案時顯示各自的進度。 + +
+ +
+狀態 + +有些狀況下很難估計進度,就可以使用 [status](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console.status) 方法,此方法會顯示「spinner」動畫及訊息。該動畫播放時,仍可正常操作主控台。參考此範例: + +```python +from time import sleep +from rich.console import Console + +console = Console() +tasks = [f"task {n}" for n in range(1, 11)] + +with console.status("[bold green]Working on tasks...") as status: + while tasks: + task = tasks.pop(0) + sleep(1) + console.log(f"{task} complete") +``` + +終端機的顯示效果如下: + +![status](https://github.com/willmcgugan/rich/raw/master/imgs/status.gif) + +該 spinner 動畫乃借用自 [cli-spinners](https://www.npmjs.com/package/cli-spinners)。可以用 `spinner` 參數指定 spinner 樣式。執行此命令以顯示可用的值: + +``` +python -m rich.spinner +``` + +此命令在終端機的輸出結果如下圖: + +![spinners](https://github.com/willmcgugan/rich/raw/master/imgs/spinners.gif) + +
+ +
+ + +Rich 可以用導引線繪製一棵 [樹](https://rich.readthedocs.io/en/latest/tree.html)。樹很適合用來顯示檔案結構,或其他繼承性的資料。 + +可以用文字或其他 Rich 能繪製的元素作為樹的標籤。執行下列程式碼來看看效果: + +``` +python -m rich.tree +``` + +這會產生下圖的結果: + +![markdown](https://github.com/willmcgugan/rich/raw/master/imgs/tree.png) + +您可以參考 [tree.py](https://github.com/willmcgugan/rich/blob/master/examples/tree.py) 範例程式,此程式可以樹狀圖展示目錄結構,如同 Linux 的 `tree` 命令。 + +
+ +
+資料欄 + +Rich 可以將內容呈現於整齊的 [資料欄](https://rich.readthedocs.io/en/latest/columns.html) 中,其欄寬可為等寬或最適寬度。此範例仿作了 macOS / Linux 系統中 `ls` 命令的基本功能,可以用資料欄列出目錄: + +```python +import os +import sys + +from rich import print +from rich.columns import Columns + +directory = os.listdir(sys.argv[1]) +print(Columns(directory)) +``` + +此螢幕截圖為 [資料欄範例](https://github.com/willmcgugan/rich/blob/master/examples/columns.py) 的輸出結果。此程式從某 API 取得資料,並以資料欄呈現: + +![columns](https://github.com/willmcgugan/rich/raw/master/imgs/columns.png) + +
+ +
+Markdown + +Rich 可以繪製 [Markdown](https://rich.readthedocs.io/en/latest/markdown.html) 並處理了將其轉換為終端機格式的大量工作。 + +先匯入 `Markdown` 類別,再以內容為 Markdown 語言的字串建構一個物件,接著將其印到 console。參考此範例: + +```python +from rich.console import Console +from rich.markdown import Markdown + +console = Console() +with open("README.md") as readme: + markdown = Markdown(readme.read()) +console.print(markdown) +``` + +執行結果如下圖: + +![markdown](https://github.com/willmcgugan/rich/raw/master/imgs/markdown.png) + +
+ +
+語法醒目標示 + +Rich 使用了 [pygments](https://pygments.org/) 函式庫來實作 [語法醒目標示](https://rich.readthedocs.io/en/latest/syntax.html) 功能。使用方式與繪製 Markdown 相似,先建構 `Syntax` 物件並將其印到 console。參考此範例: + +```python +from rich.console import Console +from rich.syntax import Syntax + +my_code = ''' +def iter_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]: + """Iterate and generate a tuple with a flag for first and last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + first = True + for value in iter_values: + yield first, False, previous_value + first = False + previous_value = value + yield first, True, previous_value +''' +syntax = Syntax(my_code, "python", theme="monokai", line_numbers=True) +console = Console() +console.print(syntax) +``` + +執行結果如下圖: + +![syntax](https://github.com/willmcgugan/rich/raw/master/imgs/syntax.png) + +
+ +
+Tracebacks(追溯) + +Rich 可以繪製 [漂亮的 tracebacks](https://rich.readthedocs.io/en/latest/traceback.html),相較標準的 Python traceback 顯示了更多程式碼且更好懂。您可以將 Rich 設為預設的 traceback handler(處理常式),如此一來所有未接住的例外都由 Rich 呈現。 + +它在 macOS 上執行的效果如圖(Linux 上差異不大): + +![traceback](https://github.com/willmcgugan/rich/raw/master/imgs/traceback.png) + +
+ +所有可由 Rich 繪製的物件都用到了 [Console 協定](https://rich.readthedocs.io/en/latest/protocol.html),您也可以依此實作自訂的 Rich 內容。 + +# Rich 企業版 + +可在 Tidelift 訂閱方案取得。 + +Rich 及其他數以千計的套件維護者正與 Tidelift 合作,以提供開放原始碼套件的商業性支援。此計畫能協助您節省時間、避開風險,同時也讓套件的維護者獲得報酬。[了解更多。](https://tidelift.com/subscription/pkg/pypi-rich?utm_source=pypi-rich&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) + +# 使用 Rich 的專案 + +以下列出幾個使用 Rich 的專案: + +- [BrancoLab/BrainRender](https://github.com/BrancoLab/BrainRender) + a python package for the visualization of three dimensional neuro-anatomical data +- [Ciphey/Ciphey](https://github.com/Ciphey/Ciphey) + Automated decryption tool +- [emeryberger/scalene](https://github.com/emeryberger/scalene) + a high-performance, high-precision CPU and memory profiler for Python +- [hedythedev/StarCli](https://github.com/hedythedev/starcli) + Browse GitHub trending projects from your command line +- [intel/cve-bin-tool](https://github.com/intel/cve-bin-tool) + This tool scans for a number of common, vulnerable components (openssl, libpng, libxml2, expat and a few others) to let you know if your system includes common libraries with known vulnerabilities. +- [nf-core/tools](https://github.com/nf-core/tools) + Python package with helper tools for the nf-core community. +- [cansarigol/pdbr](https://github.com/cansarigol/pdbr) + pdb + Rich library for enhanced debugging +- [plant99/felicette](https://github.com/plant99/felicette) + Satellite imagery for dummies. +- [seleniumbase/SeleniumBase](https://github.com/seleniumbase/SeleniumBase) + Automate & test 10x faster with Selenium & pytest. Batteries included. +- [smacke/ffsubsync](https://github.com/smacke/ffsubsync) + Automagically synchronize subtitles with video. +- [tryolabs/norfair](https://github.com/tryolabs/norfair) + Lightweight Python library for adding real-time 2D object tracking to any detector. +- [ansible/ansible-lint](https://github.com/ansible/ansible-lint) Ansible-lint checks playbooks for practices and behaviour that could potentially be improved +- [ansible-community/molecule](https://github.com/ansible-community/molecule) Ansible Molecule testing framework +- +[Many more](https://github.com/willmcgugan/rich/network/dependents)! + + diff --git a/docs/requirements.txt b/docs/requirements.txt index 273656e9f2..849854779e 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ alabaster==0.7.12 -Sphinx==4.2.0 +Sphinx==4.4.0 sphinx-rtd-theme==1.0.0 sphinx-copybutton==0.4.0 diff --git a/docs/source/conf.py b/docs/source/conf.py index 774871e3e0..34258eb496 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -43,6 +43,7 @@ "sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.napoleon", + "sphinx.ext.intersphinx", "sphinx.ext.autosectionlabel", "sphinx_copybutton", ] diff --git a/docs/source/console.rst b/docs/source/console.rst index c5de7b5e38..ed4b6c46f7 100644 --- a/docs/source/console.rst +++ b/docs/source/console.rst @@ -1,7 +1,7 @@ Console API =========== -For complete control over terminal formatting, Rich offers a :class:`~rich.console.Console` class. Most applications will require a single Console instance, so you may want to create one at the module level or as an attribute of your top-level object. For example, you could add a file called "console.py" to your project:: +For complete control over terminal formatting, Rich offers a :class:`~rich.console.Console` class. Most applications will require a single Console instance, so you may want to create one at the module level or as an attribute of your top-level object. For example, you could add a file called "console.py" to your project:: from rich.console import Console console = Console() @@ -52,7 +52,7 @@ To write rich content to the terminal use the :meth:`~rich.console.Console.print console.print(locals()) console.print("FOO", style="white on blue") -You can also use :meth:`~rich.console.Console.print` to render objects that support the :ref:`protocol`, which includes Rich's built in objects such as :class:`~rich.text.Text`, :class:`~rich.table.Table`, and :class:`~rich.syntax.Syntax` -- or other custom objects. +You can also use :meth:`~rich.console.Console.print` to render objects that support the :ref:`protocol`, which includes Rich's built-in objects such as :class:`~rich.text.Text`, :class:`~rich.table.Table`, and :class:`~rich.syntax.Syntax` -- or other custom objects. Logging @@ -140,7 +140,7 @@ Run the following command to see the available choices for ``spinner``:: Justify / Alignment ------------------- -Both print and log support a ``justify`` argument which if set must be one of "default", "left", "right", "center", or "full". If "left", any text printed (or logged) will be left aligned, if "right" text will be aligned to the right of the terminal, if "center" the text will be centered, and if "full" the text will be lined up with both the left and right edges of the terminal (like printed text in a book). +Both print and log support a ``justify`` argument which if set must be one of "default", "left", "right", "center", or "full". If "left", any text printed (or logged) will be left aligned, if "right" text will be aligned to the right of the terminal, if "center" the text will be centered, and if "full" the text will be lined up with both the left and right edges of the terminal (like printed text in a book). The default for ``justify`` is ``"default"`` which will generally look the same as ``"left"`` but with a subtle difference. Left justify will pad the right of the text with spaces, while a default justify will not. You will only notice the difference if you set a background color with the ``style`` argument. The following example demonstrates the difference:: @@ -161,7 +161,7 @@ This produces the following output:
Rich
     Rich                
-            Rich         
+            Rich        
                     Rich
     
@@ -170,7 +170,7 @@ Overflow Overflow is what happens when text you print is larger than the available space. Overflow may occur if you print long 'words' such as URLs for instance, or if you have text inside a panel or table cell with restricted space. -You can specify how Rich should handle overflow with the ``overflow`` argument to :meth:`~rich.console.Console.print` which should be one of the following strings: "fold", "crop", "ellipsis", or "ignore". The default is "fold" which will put any excess characters on the following line, creating as many new lines as required to fit the text. +You can specify how Rich should handle overflow with the ``overflow`` argument to :meth:`~rich.console.Console.print` which should be one of the following strings: "fold", "crop", "ellipsis", or "ignore". The default is "fold" which will put any excess characters on the following line, creating as many new lines as required to fit the text. The "crop" method truncates the text at the end of the line, discarding any characters that would overflow. @@ -223,7 +223,7 @@ The Console has a ``style`` attribute which you can use to apply a style to ever Soft Wrapping ------------- -Rich word wraps text you print by inserting line breaks. You can disable this behavior by setting ``soft_wrap=True`` when calling :meth:`~rich.console.Console.print`. With *soft wrapping* enabled any text that doesn't fit will run on to the following line(s), just like the builtin ``print``. +Rich word wraps text you print by inserting line breaks. You can disable this behavior by setting ``soft_wrap=True`` when calling :meth:`~rich.console.Console.print`. With *soft wrapping* enabled any text that doesn't fit will run on to the following line(s), just like the built-in ``print``. Cropping @@ -238,12 +238,14 @@ The :meth:`~rich.console.Console.print` method has a boolean ``crop`` argument. Input ----- -The console class has an :meth:`~rich.console.Console.input` which works in the same way as Python's builtin ``input()`` method, but can use anything that Rich can print as a prompt. For example, here's a colorful prompt with an emoji:: +The console class has an :meth:`~rich.console.Console.input` method which works in the same way as Python's built-in :func:`input` function, but can use anything that Rich can print as a prompt. For example, here's a colorful prompt with an emoji:: from rich.console import Console console = Console() console.input("What is [i]your[/i] [bold red]name[/]? :smiley: ") +If Python's builtin :mod:`readline` module is previously loaded, elaborate line editing and history features will be available. + Exporting --------- @@ -261,7 +263,7 @@ Error console The Console object will write to ``sys.stdout`` by default (so that you see output in the terminal). If you construct the Console with ``stderr=True`` Rich will write to ``sys.stderr``. You may want to use this to create an *error console* so you can split error messages from regular output. Here's an example:: - from rich.console import Console + from rich.console import Console error_console = Console(stderr=True) You might also want to set the ``style`` parameter on the Console to make error messages visually distinct. Here's how you might do that:: @@ -280,7 +282,7 @@ You can also tell the Console object to write to a file by setting the ``file`` with open("report.txt", "wt") as report_file: console = Console(file=report_file) console.rule(f"Report Generated {datetime.now().ctime()}") - + Note that when writing to a file you may want to explicitly the ``width`` argument if you don't want to wrap the output to the current console width. Capturing output @@ -319,7 +321,7 @@ You can page output from a Console by calling :meth:`~rich.console.Console.pager Since the default pager on most platforms don't support color, Rich will strip color from the output. If you know that your pager supports color, you can set ``styles=True`` when calling the :meth:`~rich.console.Console.pager` method. .. note:: - Rich will use the ``PAGER`` environment variable to get the pager command. On Linux and macOS you can set this to ``less -r`` to enable paging with ANSI styles. + Rich will look at ``MANPAGER`` then the ``PAGER`` environment variables (``MANPAGER`` takes priority) to get the pager command. On Linux and macOS you can set one of these to ``less -r`` to enable paging with ANSI styles. Alternate screen ---------------- @@ -338,10 +340,10 @@ Here's an example of an alternate screen:: with console.screen(): console.print(locals()) sleep(5) - + The above code will display a pretty printed dictionary on the alternate screen before returning to the command prompt after 5 seconds. -You can also provide a renderable to :meth:`~rich.console.Console.screen` which will be displayed in the alternate screen when you call :meth:`~rich.ScreenContext.update`. +You can also provide a renderable to :meth:`~rich.console.Console.screen` which will be displayed in the alternate screen when you call :meth:`~rich.ScreenContext.update`. Here's an example:: @@ -384,7 +386,7 @@ Interactive mode Rich will remove animations such as progress bars and status indicators when not writing to a terminal as you probably don't want to write these out to a text file (for example). You can override this behavior by setting the ``force_interactive`` argument on the constructor. Set it to True to enable animations or False to disable them. .. note:: - Some CI systems support ANSI color and style but not anything that moves the cursor or selectively refreshes parts of the terminal. For these you might want to set ``force_terminal`` to ``True`` and ``force_interactve`` to ``False``. + Some CI systems support ANSI color and style but not anything that moves the cursor or selectively refreshes parts of the terminal. For these you might want to set ``force_terminal`` to ``True`` and ``force_interactive`` to ``False``. Environment variables --------------------- diff --git a/docs/source/highlighting.rst b/docs/source/highlighting.rst index 260cfbeacd..03993190c9 100644 --- a/docs/source/highlighting.rst +++ b/docs/source/highlighting.rst @@ -12,8 +12,8 @@ If the default highlighting doesn't fit your needs, you can define a custom high Here's an example which highlights text that looks like an email address:: - from rich.console import Console - from rich.highlighter import RegexHighlighter + from rich.console import Console + from rich.highlighter import RegexHighlighter from rich.theme import Theme class EmailHighlighter(RegexHighlighter): diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 464ebdd65a..57d19154b5 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -10,7 +10,7 @@ Requirements Rich works with OSX, Linux and Windows. -On Windows both the (ancient) cmd.exe terminal is supported and the new `Windows Terminal `_. The later has much improved support for color and style. +On Windows both the (ancient) cmd.exe terminal is supported and the new `Windows Terminal `_. The latter has much improved support for color and style. Rich requires Python 3.6.1 and above. Note that Python 3.6.0 is *not* supported due to lack of support for methods on NamedTuples. @@ -20,7 +20,7 @@ Rich requires Python 3.6.1 and above. Note that Python 3.6.0 is *not* supported Installation ------------ -You can install Rich from PyPi with `pip` or your favorite package manager:: +You can install Rich from PyPI with `pip` or your favorite package manager:: pip install rich @@ -63,7 +63,7 @@ This writes the following output to the terminal (including all the colors and s } -If you would rather not shadow Python's builtin print, you can import ``rich.print`` as ``rprint`` (for example):: +If you would rather not shadow Python's built-in print, you can import ``rich.print`` as ``rprint`` (for example):: from rich import print as rprint @@ -103,4 +103,4 @@ Rich has an :meth:`~rich.inspect` function which can generate a report on any Py >>> from rich import inspect >>> from rich.color import Color >>> color = Color.parse("red") - >>> inspect(color, methods=True) \ No newline at end of file + >>> inspect(color, methods=True) diff --git a/docs/source/layout.rst b/docs/source/layout.rst index f230931716..480956311f 100644 --- a/docs/source/layout.rst +++ b/docs/source/layout.rst @@ -99,7 +99,7 @@ This will set the upper portion to be exactly 10 rows, no matter the size of the Ratio ----- -In addition to a fixed size, you can also make a flexible layout setting the ``ratio`` argument on the constructor or by assigning to the attribute. The ratio defines how much of the screen the layout should occupy in relation to other layouts. For example, lets reset the size and set the ratio of the upper layout to 2:: +In addition to a fixed size, you can also make a flexible layout setting the ``ratio`` argument on the constructor or by assigning to the attribute. The ratio defines how much of the screen the layout should occupy in relation to other layouts. For example, let's reset the size and set the ratio of the upper layout to 2:: layout["upper"].size = None layout["upper"].ratio = 2 @@ -124,7 +124,7 @@ The top layout is now invisible, and the "lower" layout will expand to fill the layout["upper"].visible = True print(layout) -You could use this to toggle parts of your interface based on your applications configuration. +You could use this to toggle parts of your interface based on your application's configuration. Tree ---- diff --git a/docs/source/live.rst b/docs/source/live.rst index f2c5cc76fc..51dd115d18 100644 --- a/docs/source/live.rst +++ b/docs/source/live.rst @@ -5,7 +5,7 @@ Live Display Progress bars and status indicators use a *live* display to animate parts of the terminal. You can build custom live displays with the :class:`~rich.live.Live` class. -For a demonstration of a live display, run the following command: +For a demonstration of a live display, run the following command:: python -m rich.live diff --git a/docs/source/logging.rst b/docs/source/logging.rst index b9077188ff..2b1d853f63 100644 --- a/docs/source/logging.rst +++ b/docs/source/logging.rst @@ -16,15 +16,19 @@ Here's an example of how to set up a rich logger:: log = logging.getLogger("rich") log.info("Hello, World!") -Rich logs won't render :ref:`console_markup` in logging by default as most libraries won't be aware of the need to escape literal square brackets, but you can enable it by setting ``markup=True`` on the handler. Alternatively you can enable it per log message by supplying the ``extra`` argument as follows:: +Rich logs won't render :ref:`console_markup` in logging by default as most libraries won't be aware of the need to escape literal square brackets, but you can enable it by setting ``markup=True`` on the handler. Alternatively you can enable it per log message by supplying the ``extra`` argument as follows:: log.error("[bold red blink]Server is shutting down![/]", extra={"markup": True}) +Similarly, the highlighter may be overridden per log message:: + + log.error("123 will not be highlighted", extra={"highlighter": None}) + Handle exceptions ------------------- -The :class:`~rich.logging.RichHandler` class may be configured to use Rich's :class:`~rich.traceback.Traceback` class to format exceptions, which provides more context than a builtin exception. To get beautiful exceptions in your logs set ``rich_tracebacks=True`` on the handler constructor:: +The :class:`~rich.logging.RichHandler` class may be configured to use Rich's :class:`~rich.traceback.Traceback` class to format exceptions, which provides more context than a built-in exception. To get beautiful exceptions in your logs set ``rich_tracebacks=True`` on the handler constructor:: import logging diff --git a/docs/source/markup.rst b/docs/source/markup.rst index 1ab925b089..7644bf8625 100644 --- a/docs/source/markup.rst +++ b/docs/source/markup.rst @@ -27,7 +27,7 @@ There is a shorthand for closing a style. If you omit the style name from the cl print("[bold red]Bold and red[/] not bold or red") -These markup tags may be use in combination with each other and don't need to be strictly nested. The following examples demonstrates overlapping of markup tags:: +These markup tags may be use in combination with each other and don't need to be strictly nested. The following example demonstrates overlapping of markup tags:: print("[bold]Bold[italic] bold and italic [/bold]italic[/italic]") @@ -86,7 +86,7 @@ If you add an *emoji code* to markup it will be replaced with the equivalent uni >>> print(":warning:") ⚠️ -Some emojis have two variants, the "emoji" variant displays in full color, and the "text" variant displays in monochrome (whatever your default colors are set to). You can specify the variant you want by adding either `"-emoji"` or `"-text"` to the emoji code. Here's an example: +Some emojis have two variants, the "emoji" variant displays in full color, and the "text" variant displays in monochrome (whatever your default colors are set to). You can specify the variant you want by adding either `"-emoji"` or `"-text"` to the emoji code. Here's an example: >>> from rich import print >>> print(":red_heart-emoji:") diff --git a/docs/source/pretty.rst b/docs/source/pretty.rst index 2c1c017ddd..7a050b9433 100644 --- a/docs/source/pretty.rst +++ b/docs/source/pretty.rst @@ -65,7 +65,7 @@ There are a large number of options to tweak the pretty formatting, See the :cla Rich Repr Protocol ------------------ -Rich is able to syntax highlight any output, but the formatting is restricted to builtin containers, dataclasses, and other objects Rich knows about, such as objects generated by the `attrs `_ library. To add Rich formatting capabilities to custom objects, you can implement the *rich repr protocol*. +Rich is able to syntax highlight any output, but the formatting is restricted to built-in containers, dataclasses, and other objects Rich knows about, such as objects generated by the `attrs `_ library. To add Rich formatting capabilities to custom objects, you can implement the *rich repr protocol*. Run the following command to see an example of what the Rich repr protocol can generate:: @@ -221,7 +221,7 @@ To automatically build a rich repr, use the :meth:`~rich.repr.auto` class decora from rich import print print(BIRDS) -Note that the decorator will also create a `__repr__`, so you you will get an auto-generated repr even if you don't print with Rich. +Note that the decorator will also create a `__repr__`, so you will get an auto-generated repr even if you don't print with Rich. If you want to auto-generate the angular type of repr, then set ``angular=True`` on the decorator:: diff --git a/docs/source/progress.rst b/docs/source/progress.rst index 5c70e329b6..1a4e02ed7d 100644 --- a/docs/source/progress.rst +++ b/docs/source/progress.rst @@ -59,12 +59,12 @@ Updating tasks When you call :meth:`~rich.progress.Progress.add_task` you get back a `Task ID`. Use this ID to call :meth:`~rich.progress.Progress.update` whenever you have completed some work, or any information has changed. Typically you will need to update ``completed`` every time you have completed a step. You can do this by updated ``completed`` directly or by setting ``advance`` which will add to the current ``completed`` value. -The :meth:`~rich.progress.Progress.update` method collects keyword arguments which are also associated with the task. Use this to supply any additional information you would like to render in the progress display. The additional arguments are stored in ``task.fields`` and may be referenced in :ref:`Column classes`. +The :meth:`~rich.progress.Progress.update` method collects keyword arguments which are also associated with the task. Use this to supply any additional information you would like to render in the progress display. The additional arguments are stored in ``task.fields`` and may be referenced in :ref:`Column classes`. Hiding tasks ~~~~~~~~~~~~ -You can show or hide tasks by updating the tasks ``visible`` value. Tasks are visible by default, but you can also add a invisible task by calling :meth:`~rich.progress.Progress.add_task` with ``visible=False``. +You can show or hide tasks by updating the tasks ``visible`` value. Tasks are visible by default, but you can also add an invisible task by calling :meth:`~rich.progress.Progress.add_task` with ``visible=False``. Transient progress @@ -174,7 +174,7 @@ If you have another Console object you want to use, pass it in to the :class:`~r Redirecting stdout / stderr ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To avoid breaking the progress display visuals, Rich will redirect ``stdout`` and ``stderr`` so that you can use the builtin ``print`` statement. This feature is enabled by default, but you can disable by setting ``redirect_stdout`` or ``redirect_stderr`` to ``False`` +To avoid breaking the progress display visuals, Rich will redirect ``stdout`` and ``stderr`` so that you can use the built-in ``print`` statement. This feature is enabled by default, but you can disable by setting ``redirect_stdout`` or ``redirect_stderr`` to ``False`` Customizing @@ -187,12 +187,12 @@ If the :class:`~rich.progress.Progress` class doesn't offer exactly what you nee class MyProgress(Progress): def get_renderables(self): - yield Panel(self.make_tasks_table(self.tasks)) + yield Panel(self.make_tasks_table(self.tasks)) Multiple Progress ----------------- -You can't have different columns per task with a single Progress instance. However, you can have as many Progress instance as you like in a :ref:`live`. See `live_progress.py `_ for an example of using multiple Progress instances. +You can't have different columns per task with a single Progress instance. However, you can have as many Progress instances as you like in a :ref:`live`. See `live_progress.py `_ and `dynamic_progress.py `_ for examples of using multiple Progress instances. Example ------- diff --git a/docs/source/prompt.rst b/docs/source/prompt.rst index fa256a0f64..088aa8e743 100644 --- a/docs/source/prompt.rst +++ b/docs/source/prompt.rst @@ -1,7 +1,7 @@ Prompt ====== -Rich has a number of :class:`~rich.prompt.Prompt` classes which ask a user for input and loop until a valid response is received. Here's a simple example:: +Rich has a number of :class:`~rich.prompt.Prompt` classes which ask a user for input and loop until a valid response is received (they all use the :ref:`Console API` internally). Here's a simple example:: >>> from rich.prompt import Prompt >>> name = Prompt.ask("Enter your name") diff --git a/docs/source/protocol.rst b/docs/source/protocol.rst index 796bed6467..cd8c03a4ab 100644 --- a/docs/source/protocol.rst +++ b/docs/source/protocol.rst @@ -4,7 +4,7 @@ Console Protocol ================ -Rich supports a simple protocol to add rich formatting capabilities to custom objects, so you can :meth:`~rich.console.Console.print` your object with color, styles and formatting. +Rich supports a simple protocol to add rich formatting capabilities to custom objects, so you can :meth:`~rich.console.Console.print` your object with color, styles and formatting. Use this for presentation or to display additional debugging information that might be hard to parse from a typical ``__repr__`` string. diff --git a/docs/source/style.rst b/docs/source/style.rst index b01621502b..5b8c390d58 100644 --- a/docs/source/style.rst +++ b/docs/source/style.rst @@ -106,7 +106,7 @@ You can parse a style definition explicitly with the :meth:`~rich.style.Style.pa Style Themes ------------ -If you re-use styles it can be a maintenance headache if you ever want to modify an attribute or color -- you would have to change every line where the style is used. Rich provides a :class:`~rich.theme.Theme` class which you can use to define custom styles that you can refer to by name. That way you only need update your styles in one place. +If you re-use styles it can be a maintenance headache if you ever want to modify an attribute or color -- you would have to change every line where the style is used. Rich provides a :class:`~rich.theme.Theme` class which you can use to define custom styles that you can refer to by name. That way you only need to update your styles in one place. Style themes can make your code more semantic, for instance a style called ``"warning"`` better expresses intent that ``"italic magenta underline"``. @@ -115,7 +115,7 @@ To use a style theme, construct a :class:`~rich.theme.Theme` instance and pass i from rich.console import Console from rich.theme import Theme custom_theme = Theme({ - "info" : "dim cyan", + "info": "dim cyan", "warning": "magenta", "danger": "bold red" }) @@ -132,7 +132,7 @@ To use a style theme, construct a :class:`~rich.theme.Theme` instance and pass i Customizing Defaults ~~~~~~~~~~~~~~~~~~~~ -The Theme class will inherit the default styles builtin to Rich. If your custom theme contains the name of an existing style, it will replace it. This allows you to customize the defaults as easily as you can create your own styles. For instance, here's how you can change how Rich highlights numbers:: +The Theme class will inherit the default styles built-in to Rich. If your custom theme contains the name of an existing style, it will replace it. This allows you to customize the defaults as easily as you can create your own styles. For instance, here's how you can change how Rich highlights numbers:: from rich.console import Console from rich.theme import Theme @@ -141,9 +141,10 @@ The Theme class will inherit the default styles builtin to Rich. If your custom You can disable inheriting the default theme by setting ``inherit=False`` on the :class:`rich.theme.Theme` constructor. -To see the default theme, run the following command:: +To see the default theme, run the following commands:: python -m rich.theme + python -m rich.default_styles Loading Themes diff --git a/docs/source/syntax.rst b/docs/source/syntax.rst index 823d345c8b..4e55bf9967 100644 --- a/docs/source/syntax.rst +++ b/docs/source/syntax.rst @@ -10,7 +10,7 @@ To syntax highlight code, construct a :class:`~rich.syntax.Syntax` object and pr console = Console() with open("syntax.py", "rt") as code_file: - syntax = Syntax(code_file.read(), "python") + syntax = Syntax(code_file.read(), "python") console.print(syntax) You may also use the :meth:`~rich.syntax.Syntax.from_path` alternative constructor which will load the code from disk and auto-detect the file type. The example above could be re-written as follows:: diff --git a/docs/source/tables.rst b/docs/source/tables.rst index 9048334ca9..7429ac66ac 100644 --- a/docs/source/tables.rst +++ b/docs/source/tables.rst @@ -40,7 +40,44 @@ This produces the following output: -Rich is quite smart about rendering the table. It will adjust the column widths to fit the contents and will wrap text if it doesn't fit. You can also add anything that Rich knows how to render as a title or row cell (even another table)! +Rich will calculate the optimal column sizes to fit your content, and will wrap text to fit if the terminal is not wide enough to fit the contents. + +.. note:: + You are not limited to adding text in the ``add_row`` method. You can add anything that Rich knows how to render (including another table). + +Table Options +~~~~~~~~~~~~~ + +There are a number of keyword arguments on the Table constructor you can use to define how a table should look. + +- ``title`` Sets the title of the table (text show above the table). +- ``caption`` Sets the table caption (text show below the table). +- ``width`` Sets the desired width of the table (disables automatic width calculation). +- ``min_width`` Sets a minimum width for the table. +- ``box`` Sets one of the :ref:`appendix_box` styles for the table grid, or ``None`` for no grid. +- ``safe_box`` Set to ``True`` to force the table to generate ASCII characters rather than unicode. +- ``padding`` A integer, or tuple of 1, 2, or 4 values to set the padding on cells. +- ``collapse_padding`` If True the padding of neighboring cells will be merged. +- ``pad_edge`` Set to False to remove padding around the edge of the table. +- ``expand`` Set to True to expand the table to the full available size. +- ``show_header`` Set to True to show a header, False to disable it. +- ``show_footer`` Set to True to show a footer, False to disable it. +- ``show edge`` Set to False to disable the edge line around the table. +- ``show_lines`` Set to True to show lines between rows as well as header / footer. +- ``leading`` Additional space between rows. +- ``style`` A Style to apply to the entire table, e.g. "on blue" +- ``row_styles`` Set to a list of styles to style alternating rows. e.g. ``["dim", ""]`` to create *zebra stripes* +- ``header_style`` Set the default style for the header. +- ``footer_style`` Set the default style for the footer. +- ``border style`` Set a style for border characters. +- ``title_style`` Set a style for the title. +- ``caption_style`` Set a style for the caption. +- ``title_justify`` Set the title justify method ("left", "right", "center", or "full") +- ``caption_justify`` Set the caption justify method ("left", "right", "center", or "full") +- ``highlight`` Set to True to enable automatic highlighting of cell contents. + +Border Styles +~~~~~~~~~~~~~ You can set the border style by importing one of the preset :class:`~rich.box.Box` objects and setting the ``box`` argument in the table constructor. Here's an example that modifies the look of the Star Wars table:: @@ -49,9 +86,17 @@ You can set the border style by importing one of the preset :class:`~rich.box.Bo See :ref:`appendix_box` for other box styles. +You can also set ``box=None`` to remove borders entirely. + The :class:`~rich.table.Table` class offers a number of configuration options to set the look and feel of the table, including how borders are rendered and the style and alignment of the columns. +Lines +~~~~~ + +By default, Tables will show a line under the header only. If you want to show lines between all rows add ``show_lines=True`` to the constructor. + + Empty Tables ~~~~~~~~~~~~ @@ -70,7 +115,7 @@ You may also add columns by specifying them in the positional arguments of the : table = Table("Released", "Title", "Box Office", title="Star Wars Movies") -This allows you to specify the text of the column only. If you want to set other attributes, such as width and style, you can add an :class:`~rich.table.Column` class. Here's an example:: +This allows you to specify the text of the column only. If you want to set other attributes, such as width and style, you can add a :class:`~rich.table.Column` class. Here's an example:: from rich.table import Column table = Table( @@ -80,10 +125,29 @@ This allows you to specify the text of the column only. If you want to set other title="Star Wars Movies" ) -Lines -~~~~~ +Column Options +~~~~~~~~~~~~~~ -By default, Tables will show a line under the header only. If you want to show lines between all rows add ``show_lines=True`` to the constructor. +There are a number of options you can set on a column to modify how it will look. + +- ``header_style`` Sets the style of the header, e.g. "bold magenta". +- ``footer_style`` Sets the style of the footer. +- ``style`` Sets a style that applies to the column. You could use this to highlight a column by setting the background with "on green" for example. +- ``justify`` Sets the text justify to one of "left", "center", "right", or "full". +- ``vertical`` Sets the vertical alignment of the cells in a column, to one of "top", "middle", or "bottom". +- ``width`` Explicitly set the width of a row to a given number of characters (disables automatic calculation). +- ``min_width`` When set to an integer will prevent the column from shrinking below this amount. +- ``max_width`` When set to an integer will prevent the column from growing beyond this amount. +- ``ratio`` Defines a ratio to set the column width. For instance, if there are 3 columns with a total of 6 ratio, and ``ratio=2`` then the column will be a third of the available size. +- ``no_wrap`` Set to True to prevent this column from wrapping. + +Vertical Alignment +~~~~~~~~~~~~~~~~~~ + +You can define the vertical alignment of a column by setting the ``vertical`` parameter of the column. You can also do this per-cell by wrapping your text or renderable with a :class:`~rich.align.Align` class:: + + + table.add_row(Align("Title", vertical="middle")) Grids ~~~~~ diff --git a/docs/source/text.rst b/docs/source/text.rst index b5f2eaf8f1..fd6851fb4f 100644 --- a/docs/source/text.rst +++ b/docs/source/text.rst @@ -5,7 +5,7 @@ Rich Text Rich has a :class:`~rich.text.Text` class you can use to mark up strings with color and style attributes. You can use a Text instance anywhere a string is accepted, which gives you a lot of control over presentation. -You can consider this class to be like a string with marked up regions of text. Unlike a builtin ``str``, a Text instance is mutable, and most methods operate in-place rather than returning a new instance. +You can consider this class to be like a string with marked up regions of text. Unlike a built-in ``str``, a Text instance is mutable, and most methods operate in-place rather than returning a new instance. One way to add a style to Text is the :meth:`~rich.text.Text.stylize` method which applies a style to a start and end offset. Here is an example:: @@ -26,6 +26,11 @@ Alternatively, you can construct styled text by calling :meth:`~rich.text.Text.a text.append(" World!") console.print(text) +If you would like to use text that is already formatted with ANSI codes, call :meth:`~rich.text.Text.from_ansi` to convert it to a ``Text`` object:: + + text = Text.from_ansi("\033[1mHello, World!\033[0m") + console.print(text.spans) + Since building Text instances from parts is a common requirement, Rich offers :meth:`~rich.text.Text.assemble` which will combine strings or pairs of string and Style, and return a Text instance. The follow example is equivalent to the code above:: text = Text.assemble(("Hello", "bold magenta"), " World!") diff --git a/docs/source/traceback.rst b/docs/source/traceback.rst index 6b1a6f9cec..f940bbbeae 100644 --- a/docs/source/traceback.rst +++ b/docs/source/traceback.rst @@ -18,7 +18,7 @@ The :meth:`~rich.console.Console.print_exception` method will print a traceback try: do_something() - except Exception: + except Exception: console.print_exception(show_locals=True) The ``show_locals=True`` parameter causes Rich to display the value of local variables for each frame of the traceback. @@ -26,7 +26,7 @@ The ``show_locals=True`` parameter causes Rich to display the value of local var See `exception.py `_ for a larger example. -Traceback handler +Traceback Handler ----------------- Rich can be installed as the default traceback handler so that all uncaught exceptions will be rendered with highlighting. Here's how:: @@ -36,6 +36,27 @@ Rich can be installed as the default traceback handler so that all uncaught exce There are a few options to configure the traceback handler, see :func:`~rich.traceback.install` for details. +Automatic Traceback Handler +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In some cases you may want to have the traceback handler installed automatically without having to worry about importing the code in your module. You can do that by modifying the `sitecustomize.py` in your virtual environment. Typically it would be located in your virtual environment path, underneath the `site-packages` folder, something like this:: + + ./.venv/lib/python3.9/site-packages/sitecustomize.py + +In most cases this file will not exist. If it doesn't exist, you can create it by:: + + $ touch .venv/lib/python3.9/site-packages/sitecustomize.py + +Add the following code to the file:: + + from rich.traceback import install + install(show_locals=True) + +At this point, the traceback will be installed for any code that is run within the virtual environment. + +.. note:: + If you plan on sharing your code, it is probably best to include the traceback install in your main entry point module. + Suppressing Frames ------------------ @@ -55,7 +76,7 @@ Max Frames A recursion error can generate very large tracebacks that take a while to render and contain a lot of repetitive frames. Rich guards against this with a `max_frames` argument, which defaults to 100. If a traceback contains more than 100 frames then only the first 50, and last 50 will be shown. You can disable this feature by setting `max_frames` to 0. -Here's an example of printing an recursive error:: +Here's an example of printing a recursive error:: from rich.console import Console @@ -73,4 +94,5 @@ Here's an example of printing an recursive error:: try: foo(1) except Exception: - console.print_exception(max_frames=20) \ No newline at end of file + console.print_exception(max_frames=20) + diff --git a/examples/downloader.py b/examples/downloader.py index 15697a49d5..3671cee48b 100644 --- a/examples/downloader.py +++ b/examples/downloader.py @@ -73,7 +73,7 @@ def download(urls: Iterable[str], dest_dir: str): if __name__ == "__main__": - # Try with https://releases.ubuntu.com/20.04/ubuntu-20.04.2.0-desktop-amd64.iso + # Try with https://releases.ubuntu.com/20.04/ubuntu-20.04.3-desktop-amd64.iso if sys.argv[1:]: download(sys.argv[1:], "./") else: diff --git a/examples/dynamic_progress.py b/examples/dynamic_progress.py new file mode 100644 index 0000000000..c38da2ebf0 --- /dev/null +++ b/examples/dynamic_progress.py @@ -0,0 +1,118 @@ +""" + +Demonstrates how to create a dynamic group of progress bars, +showing multi-level progress for multiple tasks (installing apps in the example), +each of which consisting of multiple steps. + +""" + +import time + +from rich.console import Group +from rich.panel import Panel +from rich.live import Live +from rich.progress import ( + BarColumn, + Progress, + SpinnerColumn, + TextColumn, + TimeElapsedColumn, +) + + +def run_steps(name, step_times, app_steps_task_id): + """Run steps for a single app, and update corresponding progress bars.""" + + for idx, step_time in enumerate(step_times): + # add progress bar for this step (time elapsed + spinner) + action = step_actions[idx] + step_task_id = step_progress.add_task("", action=action, name=name) + + # run steps, update progress + for _ in range(step_time): + time.sleep(0.5) + step_progress.update(step_task_id, advance=1) + + # stop and hide progress bar for this step when done + step_progress.stop_task(step_task_id) + step_progress.update(step_task_id, visible=False) + + # also update progress bar for current app when step is done + app_steps_progress.update(app_steps_task_id, advance=1) + + +# progress bar for current app showing only elapsed time, +# which will stay visible when app is installed +current_app_progress = Progress( + TimeElapsedColumn(), + TextColumn("{task.description}"), +) + +# progress bars for single app steps (will be hidden when step is done) +step_progress = Progress( + TextColumn(" "), + TimeElapsedColumn(), + TextColumn("[bold purple]{task.fields[action]}"), + SpinnerColumn("simpleDots"), +) +# progress bar for current app (progress in steps) +app_steps_progress = Progress( + TextColumn( + "[bold blue]Progress for app {task.fields[name]}: {task.percentage:.0f}%" + ), + BarColumn(), + TextColumn("({task.completed} of {task.total} steps done)"), +) +# overall progress bar +overall_progress = Progress( + TimeElapsedColumn(), BarColumn(), TextColumn("{task.description}") +) +# group of progress bars; +# some are always visible, others will disappear when progress is complete +progress_group = Group( + Panel(Group(current_app_progress, step_progress, app_steps_progress)), + overall_progress, +) + +# tuple specifies how long each step takes for that app +step_actions = ("downloading", "configuring", "building", "installing") +apps = [ + ("one", (2, 1, 4, 2)), + ("two", (1, 3, 8, 4)), + ("three", (2, 1, 3, 2)), +] + +# create overall progress bar +overall_task_id = overall_progress.add_task("", total=len(apps)) + +# use own live instance as context manager with group of progress bars, +# which allows for running multiple different progress bars in parallel, +# and dynamically showing/hiding them +with Live(progress_group): + + for idx, (name, step_times) in enumerate(apps): + # update message on overall progress bar + top_descr = "[bold #AAAAAA](%d out of %d apps installed)" % (idx, len(apps)) + overall_progress.update(overall_task_id, description=top_descr) + + # add progress bar for steps of this app, and run the steps + current_task_id = current_app_progress.add_task("Installing app %s" % name) + app_steps_task_id = app_steps_progress.add_task( + "", total=len(step_times), name=name + ) + run_steps(name, step_times, app_steps_task_id) + + # stop and hide steps progress bar for this specific app + app_steps_progress.update(app_steps_task_id, visible=False) + current_app_progress.stop_task(current_task_id) + current_app_progress.update( + current_task_id, description="[bold green]App %s installed!" % name + ) + + # increase overall progress now this task is done + overall_progress.update(overall_task_id, advance=1) + + # final update for message on overall progress bar + overall_progress.update( + overall_task_id, description="[bold green]%s apps installed, done!" % len(apps) + ) diff --git a/examples/export.py b/examples/export.py new file mode 100644 index 0000000000..dada493d80 --- /dev/null +++ b/examples/export.py @@ -0,0 +1,56 @@ +""" +Demonstrates export console output +""" + +from rich.console import Console +from rich.table import Table + +console = Console(record=True) + + +def print_table(): + table = Table(title="Star Wars Movies") + + table.add_column("Released", style="cyan", no_wrap=True) + table.add_column("Title", style="magenta") + table.add_column("Box Office", justify="right", style="green") + + table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690") + table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") + table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889") + table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889") + + console.print(table) + + +# Prints table +print_table() + +# Get console output as text +file1 = "table_export_plaintext.txt" +text = console.export_text() +with open(file1, "w") as file: + file.write(text) +print(f"Exported console output as plain text to {file1}") + +# Calling print_table again because console output buffer +# is flushed once export function is called +print_table() + +# Get console output as html +# use clear=False so output is not flushed after export +file2 = "table_export_html.html" +html = console.export_html(clear=False) +with open(file2, "w") as file: + file.write(html) +print(f"Exported console output as html to {file2}") + +# Export text output to table_export.txt +file3 = "table_export_plaintext2.txt" +console.save_text(file3, clear=False) +print(f"Exported console output as plain text to {file3}") + +# Export html output to table_export.html +file4 = "table_export_html2.html" +console.save_html(file4) +print(f"Exported console output as html to {file4}") diff --git a/examples/print_calendar.py b/examples/print_calendar.py new file mode 100644 index 0000000000..d9586aaa8a --- /dev/null +++ b/examples/print_calendar.py @@ -0,0 +1,70 @@ +""" +Builds calendar layout using Columns and Tables. +Usage: +python print_calendar.py [YEAR] +Example: +python print_calendar.py 2021 +""" +import argparse +import calendar +from datetime import datetime + +from rich.align import Align +from rich import box +from rich.columns import Columns +from rich.console import Console +from rich.table import Table +from rich.text import Text + + +def print_calendar(year): + """Print a calendar for a given year.""" + + today = datetime.today() + year = int(year) + cal = calendar.Calendar() + today_tuple = today.day, today.month, today.year + + tables = [] + + for month in range(1, 13): + table = Table( + title=f"{calendar.month_name[month]} {year}", + style="green", + box=box.SIMPLE_HEAVY, + padding=0, + ) + + for week_day in cal.iterweekdays(): + table.add_column( + "{:.3}".format(calendar.day_name[week_day]), justify="right" + ) + + month_days = cal.monthdayscalendar(year, month) + for weekdays in month_days: + days = [] + for index, day in enumerate(weekdays): + day_label = Text(str(day or ""), style="magenta") + if index in (5, 6): + day_label.stylize("blue") + if day and (day, month, year) == today_tuple: + day_label.stylize("white on dark_red") + days.append(day_label) + table.add_row(*days) + + tables.append(Align.center(table)) + + console = Console() + columns = Columns(tables, padding=1, expand=True) + console.rule(str(year)) + console.print() + console.print(columns) + console.rule(str(year)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Rich calendar") + parser.add_argument("year", metavar="year", type=int) + args = parser.parse_args() + + print_calendar(args.year) diff --git a/examples/table_movie.py b/examples/table_movie.py index 515909d9c9..811eb51b70 100644 --- a/examples/table_movie.py +++ b/examples/table_movie.py @@ -6,7 +6,6 @@ from rich.align import Align from rich.console import Console from rich.live import Live -from rich.measure import Measurement from rich.table import Table from rich.text import Text diff --git a/poetry.lock b/poetry.lock index 9bd5f60d42..59c3236d6d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3,25 +3,42 @@ name = "appnope" version = "0.1.2" description = "Disable App Nap on macOS >= 10.9" category = "main" -optional = true +optional = false python-versions = "*" [[package]] name = "argon2-cffi" -version = "21.1.0" +version = "21.3.0" description = "The secure Argon2 password hashing algorithm." category = "main" optional = true -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -cffi = ">=1.0.0" +argon2-cffi-bindings = "*" +dataclasses = {version = "*", markers = "python_version < \"3.7\""} +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest", "sphinx", "furo", "wheel", "pre-commit"] -docs = ["sphinx", "furo"] +dev = ["pre-commit", "cogapp", "tomli", "coverage[toml] (>=5.0.2)", "hypothesis", "pytest", "sphinx", "sphinx-notfound-page", "furo"] +docs = ["sphinx", "sphinx-notfound-page", "furo"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +category = "main" +optional = true +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["pytest", "cogapp", "pre-commit", "wheel"] +tests = ["pytest"] + [[package]] name = "async-generator" version = "1.10" @@ -40,29 +57,29 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.2.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "backcall" version = "0.2.0" description = "Specifications for callback functions passed in to an API" category = "main" -optional = true +optional = false python-versions = "*" [[package]] name = "black" -version = "21.9b0" +version = "21.12b0" description = "The uncompromising code formatter." category = "dev" optional = false @@ -74,9 +91,8 @@ dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0,<1" platformdirs = ">=2" -regex = ">=2020.1.8" tomli = ">=0.2.6,<2.0.0" -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = [ {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, @@ -84,9 +100,9 @@ typing-extensions = [ [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] +d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -python2 = ["typed-ast (>=1.4.2)"] +python2 = ["typed-ast (>=1.4.3)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] @@ -104,7 +120,7 @@ webencodings = "*" [[package]] name = "cffi" -version = "1.14.6" +version = "1.15.0" description = "Foreign Function Interface for Python calling C code." category = "main" optional = true @@ -113,9 +129,17 @@ python-versions = "*" [package.dependencies] pycparser = "*" +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + [[package]] name = "click" -version = "8.0.1" +version = "8.0.3" description = "Composable command line interface toolkit" category = "dev" optional = false @@ -146,12 +170,15 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "coverage" -version = "6.0" +version = "6.2" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.6" +[package.dependencies] +tomli = {version = "*", optional = true, markers = "extra == \"toml\""} + [package.extras] toml = ["tomli"] @@ -168,7 +195,7 @@ name = "decorator" version = "5.1.0" description = "Decorators for Humans" category = "main" -optional = true +optional = false python-versions = ">=3.5" [[package]] @@ -179,6 +206,14 @@ category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "distlib" +version = "0.3.4" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "entrypoints" version = "0.3" @@ -187,9 +222,32 @@ category = "main" optional = true python-versions = ">=2.7" +[[package]] +name = "filelock" +version = "3.4.1" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] + +[[package]] +name = "identify" +version = "2.4.1" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.extras] +license = ["ukkonen"] + [[package]] name = "importlib-metadata" -version = "4.8.1" +version = "4.8.2" description = "Read metadata from Python packages" category = "main" optional = false @@ -202,7 +260,22 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "importlib-resources" +version = "5.2.3" +description = "Read resources from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] name = "iniconfig" @@ -214,7 +287,7 @@ python-versions = "*" [[package]] name = "ipykernel" -version = "5.5.5" +version = "5.5.6" description = "IPython Kernel for Jupyter" category = "main" optional = true @@ -223,6 +296,7 @@ python-versions = ">=3.5" [package.dependencies] appnope = {version = "*", markers = "platform_system == \"Darwin\""} ipython = ">=5.0.0" +ipython-genutils = "*" jupyter-client = "*" tornado = ">=4.2" traitlets = ">=4.1.0" @@ -232,10 +306,10 @@ test = ["pytest (!=5.3.4)", "pytest-cov", "flaky", "nose", "jedi (<=0.17.2)"] [[package]] name = "ipython" -version = "7.16.1" +version = "7.16.3" description = "IPython: Productive Interactive Computing" category = "main" -optional = true +optional = false python-versions = ">=3.6" [package.dependencies] @@ -243,7 +317,7 @@ appnope = {version = "*", markers = "sys_platform == \"darwin\""} backcall = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" -jedi = ">=0.10" +jedi = ">=0.10,<=0.17.2" pexpect = {version = "*", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" @@ -266,7 +340,7 @@ name = "ipython-genutils" version = "0.2.0" description = "Vestigial utilities from IPython" category = "main" -optional = true +optional = false python-versions = "*" [[package]] @@ -291,22 +365,22 @@ test = ["pytest (>=3.6.0)", "pytest-cov", "mock"] [[package]] name = "jedi" -version = "0.18.0" +version = "0.17.2" description = "An autocompletion tool for Python that can be used for text editors." category = "main" -optional = true -python-versions = ">=3.6" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -parso = ">=0.8.0,<0.9.0" +parso = ">=0.7.0,<0.8.0" [package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] +qa = ["flake8 (==3.7.9)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] [[package]] name = "jinja2" -version = "3.0.2" +version = "3.0.3" description = "A very fast and expressive template engine." category = "main" optional = true @@ -337,7 +411,7 @@ format_nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jupyter-client" -version = "7.0.6" +version = "7.1.0" description = "Jupyter protocol implementation and client libraries" category = "main" optional = true @@ -358,7 +432,7 @@ test = ["codecov", "coverage", "ipykernel", "ipython", "mock", "mypy", "pre-comm [[package]] name = "jupyter-core" -version = "4.8.1" +version = "4.9.1" description = "Jupyter core package. A base package on which Jupyter projects rely." category = "main" optional = true @@ -405,21 +479,21 @@ python-versions = "*" [[package]] name = "mypy" -version = "0.910" +version = "0.930" description = "Optional static typing for Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -toml = "*" -typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.7.4" +mypy-extensions = ">=0.4.3" +tomli = ">=1.1.0" +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<1.5.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] [[package]] name = "mypy-extensions" @@ -431,7 +505,7 @@ python-versions = "*" [[package]] name = "nbclient" -version = "0.5.4" +version = "0.5.9" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." category = "main" optional = true @@ -445,9 +519,9 @@ nest-asyncio = "*" traitlets = ">=4.2" [package.extras] -dev = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "bumpversion", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"] +dev = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"] sphinx = ["Sphinx (>=1.7)", "sphinx-book-theme", "mock", "moto", "myst-parser"] -test = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "bumpversion", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"] +test = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"] [[package]] name = "nbconvert" @@ -499,15 +573,23 @@ test = ["check-manifest", "fastjsonschema", "testpath", "pytest", "pytest-cov"] [[package]] name = "nest-asyncio" -version = "1.5.1" +version = "1.5.4" description = "Patch asyncio to allow nested event loops" category = "main" optional = true python-versions = ">=3.5" +[[package]] +name = "nodeenv" +version = "1.6.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "notebook" -version = "6.4.4" +version = "6.4.6" description = "A web-based notebook environment for interactive computing" category = "main" optional = true @@ -522,9 +604,10 @@ jupyter-client = ">=5.3.4" jupyter-core = ">=4.6.1" nbconvert = "*" nbformat = "*" +nest-asyncio = ">=1.5" prometheus-client = "*" pyzmq = ">=17" -Send2Trash = ">=1.5.0" +Send2Trash = ">=1.8.0" terminado = ">=0.8.3" tornado = ">=6.1" traitlets = ">=4.2.1" @@ -536,14 +619,14 @@ test = ["pytest", "coverage", "requests", "nbval", "selenium", "pytest-cov", "re [[package]] name = "packaging" -version = "21.0" +version = "21.3" description = "Core utilities for Python packages" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pandocfilters" @@ -555,15 +638,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "parso" -version = "0.8.2" +version = "0.7.1" description = "A Python Parser" category = "main" -optional = true -python-versions = ">=3.6" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["docopt", "pytest (<6.0.0)"] +testing = ["docopt", "pytest (>=3.0.7)"] [[package]] name = "pathspec" @@ -578,7 +660,7 @@ name = "pexpect" version = "4.8.0" description = "Pexpect allows easy control of interactive console applications." category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -589,7 +671,7 @@ name = "pickleshare" version = "0.7.5" description = "Tiny 'shelve'-like database with concurrency support" category = "main" -optional = true +optional = false python-versions = "*" [[package]] @@ -619,9 +701,27 @@ importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "2.16.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-resources = {version = "<5.3", markers = "python_version < \"3.7\""} +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + [[package]] name = "prometheus-client" -version = "0.11.0" +version = "0.12.0" description = "Python client for the Prometheus monitoring system." category = "main" optional = true @@ -632,10 +732,10 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.20" +version = "3.0.24" description = "Library for building powerful interactive command lines in Python" category = "main" -optional = true +optional = false python-versions = ">=3.6.2" [package.dependencies] @@ -646,20 +746,20 @@ name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" category = "main" -optional = true +optional = false python-versions = "*" [[package]] name = "py" -version = "1.10.0" +version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pycparser" -version = "2.20" +version = "2.21" description = "C parser in Python" category = "main" optional = true @@ -667,7 +767,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.10.0" +version = "2.11.2" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false @@ -675,11 +775,14 @@ python-versions = ">=3.5" [[package]] name = "pyparsing" -version = "2.4.7" +version = "3.0.6" description = "Python parsing module" category = "main" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyrsistent" @@ -713,16 +816,15 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-cov" -version = "2.12.1" +version = "3.0.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] -coverage = ">=5.2.1" +coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" -toml = "*" [package.extras] testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] @@ -740,7 +842,7 @@ six = ">=1.5" [[package]] name = "pywin32" -version = "301" +version = "302" description = "Python for Window Extensions" category = "main" optional = true @@ -748,12 +850,20 @@ python-versions = "*" [[package]] name = "pywinpty" -version = "1.1.4" +version = "1.1.6" description = "Pseudo terminal support for Windows from Python." category = "main" optional = true python-versions = ">=3.6" +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "pyzmq" version = "22.3.0" @@ -766,14 +876,6 @@ python-versions = ">=3.6" cffi = {version = "*", markers = "implementation_name == \"pypy\""} py = {version = "*", markers = "implementation_name == \"pypy\""} -[[package]] -name = "regex" -version = "2021.9.30" -description = "Alternative regular expression module, to replace re." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "send2trash" version = "1.8.0" @@ -792,7 +894,7 @@ name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "main" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] @@ -832,7 +934,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "1.2.1" +version = "1.2.2" description = "A lil' TOML parser" category = "dev" optional = false @@ -851,7 +953,7 @@ name = "traitlets" version = "4.3.3" description = "Traitlets Python config system" category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -872,7 +974,7 @@ python-versions = "*" [[package]] name = "types-dataclasses" -version = "0.1.7" +version = "0.6.4" description = "Typing stubs for dataclasses" category = "dev" optional = false @@ -880,18 +982,38 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" + +[[package]] +name = "virtualenv" +version = "20.13.0" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +distlib = ">=0.3.1,<1" +filelock = ">=3.2,<4" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} +platformdirs = ">=2,<3" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] [[package]] name = "wcwidth" version = "0.2.5" description = "Measures the displayed width of unicode strings in a terminal" category = "main" -optional = true +optional = false python-versions = "*" [[package]] @@ -904,7 +1026,7 @@ python-versions = "*" [[package]] name = "widgetsnbextension" -version = "3.5.1" +version = "3.5.2" description = "IPython HTML widgets for Jupyter" category = "main" optional = true @@ -931,7 +1053,7 @@ jupyter = ["ipywidgets"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "97432dbcf77dd3aaa24f0cd35a7e70df03b64fe3b0397618bcb566507dcc40c3" +content-hash = "d642a2a33643ec67d8ec48fef8ac01b7226829d0dcc3a14b10c3140005cbd0f0" [metadata.files] appnope = [ @@ -939,17 +1061,31 @@ appnope = [ {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, ] argon2-cffi = [ - {file = "argon2-cffi-21.1.0.tar.gz", hash = "sha256:f710b61103d1a1f692ca3ecbd1373e28aa5e545ac625ba067ff2feca1b2bb870"}, - {file = "argon2_cffi-21.1.0-cp35-abi3-macosx_10_14_x86_64.whl", hash = "sha256:217b4f0f853ccbbb5045242946ad2e162e396064575860141b71a85eb47e475a"}, - {file = "argon2_cffi-21.1.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa7e7d1fc22514a32b1761fdfa1882b6baa5c36bb3ef557bdd69e6fc9ba14a41"}, - {file = "argon2_cffi-21.1.0-cp35-abi3-win32.whl", hash = "sha256:e4d8f0ae1524b7b0372a3e574a2561cbdddb3fdb6c28b70a72868189bda19659"}, - {file = "argon2_cffi-21.1.0-cp35-abi3-win_amd64.whl", hash = "sha256:65213a9174320a1aee03fe826596e0620783966b49eb636955958b3074e87ff9"}, - {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-macosx_10_7_x86_64.whl", hash = "sha256:245f64a203012b144b7b8c8ea6d468cb02b37caa5afee5ba4a10c80599334f6a"}, - {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4ad152c418f7eb640eac41ac815534e6aa61d1624530b8e7779114ecfbf327f8"}, - {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:bc513db2283c385ea4da31a2cd039c33380701f376f4edd12fe56db118a3b21a"}, - {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c7a7c8cc98ac418002090e4add5bebfff1b915ea1cb459c578cd8206fef10378"}, - {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:165cadae5ac1e26644f5ade3bd9c18d89963be51d9ea8817bd671006d7909057"}, - {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:566ffb581bbd9db5562327aee71b2eda24a1c15b23a356740abe3c011bbe0dcb"}, + {file = "argon2-cffi-21.3.0.tar.gz", hash = "sha256:d384164d944190a7dd7ef22c6aa3ff197da12962bd04b17f64d4e93d934dba5b"}, + {file = "argon2_cffi-21.3.0-py3-none-any.whl", hash = "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80"}, +] +argon2-cffi-bindings = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, ] async-generator = [ {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, @@ -960,71 +1096,80 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] black = [ - {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"}, - {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"}, + {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, + {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, ] bleach = [ {file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"}, {file = "bleach-4.1.0.tar.gz", hash = "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da"}, ] cffi = [ - {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"}, - {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"}, - {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"}, - {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"}, - {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"}, - {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"}, - {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"}, - {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"}, - {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"}, - {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"}, - {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"}, - {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"}, - {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"}, - {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"}, - {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"}, - {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"}, - {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"}, - {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, - {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, + {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, + {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, + {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, + {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, + {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, + {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, + {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, + {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, + {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, + {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, + {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, + {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, + {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, + {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, + {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, + {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, + {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, +] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] click = [ - {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, - {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -1035,41 +1180,53 @@ commonmark = [ {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, ] coverage = [ - {file = "coverage-6.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:3dfb23cc180b674a11a559183dff9655beb9da03088f3fe3c4f3a6d200c86f05"}, - {file = "coverage-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5dd5ae0a9cd55d71f1335c331e9625382239b8cede818fb62d8d2702336dbf8"}, - {file = "coverage-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8426fec5ad5a6e8217921716b504e9b6e1166dc147e8443b4855e329db686282"}, - {file = "coverage-6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aa5d4d43fa18cc9d0c6e02a83de0b9729b5451a9066574bd276481474f0a53ab"}, - {file = "coverage-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78dd3eeb8f5ff26d2113c41836bac04a9ea91be54c346826b54a373133c8c53"}, - {file = "coverage-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:581fddd2f883379bd5af51da9233e0396b6519f3d3eeae4fb88867473be6d56e"}, - {file = "coverage-6.0-cp310-cp310-win32.whl", hash = "sha256:43bada49697a62ffa0283c7f01bbc76aac562c37d4bb6c45d56dd008d841194e"}, - {file = "coverage-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:fa816e97cfe1f691423078dffa39a18106c176f28008db017b3ce3e947c34aa5"}, - {file = "coverage-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:5c191e01b23e760338f19d8ba2470c0dad44c8b45e41ac043b2db84efc62f695"}, - {file = "coverage-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274a612f67f931307706b60700f1e4cf80e1d79dff6c282fc9301e4565e78724"}, - {file = "coverage-6.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9dbfcbc56d8de5580483cf2caff6a59c64d3e88836cbe5fb5c20c05c29a8808"}, - {file = "coverage-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e63490e8a6675cee7a71393ee074586f7eeaf0e9341afd006c5d6f7eec7c16d7"}, - {file = "coverage-6.0-cp36-cp36m-win32.whl", hash = "sha256:72f8c99f1527c5a8ee77c890ea810e26b39fd0b4c2dffc062e20a05b2cca60ef"}, - {file = "coverage-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:88f1810eb942e7063d051d87aaaa113eb5fd5a7fd2cda03a972de57695b8bb1a"}, - {file = "coverage-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:befb5ffa9faabef6dadc42622c73de168001425258f0b7e402a2934574e7a04b"}, - {file = "coverage-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7dbda34e8e26bd86606ba8a9c13ccb114802e01758a3d0a75652ffc59a573220"}, - {file = "coverage-6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b4ee5815c776dfa3958ba71c7cd4cdd8eb40d79358a18352feb19562fe4408c4"}, - {file = "coverage-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d82cbef1220703ce56822be7fbddb40736fc1a928ac893472df8aff7421ae0aa"}, - {file = "coverage-6.0-cp37-cp37m-win32.whl", hash = "sha256:d795a2c92fe8cb31f6e9cd627ee4f39b64eb66bf47d89d8fcf7cb3d17031c887"}, - {file = "coverage-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6e216e4021c934246c308fd3e0d739d9fa8a3f4ea414f584ab90ef9c1592f282"}, - {file = "coverage-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8305e14112efb74d0b5fec4df6e41cafde615c2392a7e51c84013cafe945842c"}, - {file = "coverage-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4865dc4a7a566147cbdc2b2f033a6cccc99a7dcc89995137765c384f6c73110b"}, - {file = "coverage-6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:25df2bc53a954ba2ccf230fa274d1de341f6aa633d857d75e5731365f7181749"}, - {file = "coverage-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:08fd55d2e00dac4c18a2fa26281076035ec86e764acdc198b9185ce749ada58f"}, - {file = "coverage-6.0-cp38-cp38-win32.whl", hash = "sha256:11ce082eb0f7c2bbfe96f6c8bcc3a339daac57de4dc0f3186069ec5c58da911c"}, - {file = "coverage-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:7844a8c6a0fee401edbf578713c2473e020759267c40261b294036f9d3eb6a2d"}, - {file = "coverage-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bea681309bdd88dd1283a8ba834632c43da376d9bce05820826090aad80c0126"}, - {file = "coverage-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e735ab8547d8a1fe8e58dd765d6f27ac539b395f52160d767b7189f379f9be7a"}, - {file = "coverage-6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7593a49300489d064ebb6c58539f52cbbc4a2e6a4385de5e92cae1563f88a425"}, - {file = "coverage-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:adb0f4c3c8ba8104378518a1954cbf3d891a22c13fd0e0bf135391835f44f288"}, - {file = "coverage-6.0-cp39-cp39-win32.whl", hash = "sha256:8da0c4a26a831b392deaba5fdd0cd7838d173b47ce2ec3d0f37be630cb09ef6e"}, - {file = "coverage-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:7af2f8e7bb54ace984de790e897f858e88068d8fbc46c9490b7c19c59cf51822"}, - {file = "coverage-6.0-pp36-none-any.whl", hash = "sha256:82b58d37c47d93a171be9b5744bcc96a0012cbf53d5622b29a49e6be2097edd7"}, - {file = "coverage-6.0-pp37-none-any.whl", hash = "sha256:fff04bfefb879edcf616f1ce5ea6f4a693b5976bdc5e163f8464f349c25b59f0"}, - {file = "coverage-6.0.tar.gz", hash = "sha256:17983f6ccc47f4864fd16d20ff677782b23d1207bf222d10e4d676e4636b0872"}, + {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, + {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, + {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, + {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, + {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, + {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, + {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, + {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, + {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, + {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, + {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, + {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, + {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, + {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, + {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, + {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, + {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, + {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, + {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, + {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, ] dataclasses = [ {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, @@ -1083,25 +1240,41 @@ defusedxml = [ {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] +distlib = [ + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, +] entrypoints = [ {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, ] +filelock = [ + {file = "filelock-3.4.1-py3-none-any.whl", hash = "sha256:a4bc51381e01502a30e9f06dd4fa19a1712eab852b6fb0f84fd7cce0793d8ca3"}, + {file = "filelock-3.4.1.tar.gz", hash = "sha256:0f12f552b42b5bf60dba233710bf71337d35494fc8bdd4fd6d9f6d082ad45e06"}, +] +identify = [ + {file = "identify-2.4.1-py2.py3-none-any.whl", hash = "sha256:0192893ff68b03d37fed553e261d4a22f94ea974093aefb33b29df2ff35fed3c"}, + {file = "identify-2.4.1.tar.gz", hash = "sha256:64d4885e539f505dd8ffb5e93c142a1db45480452b1594cacd3e91dca9a984e9"}, +] importlib-metadata = [ - {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, - {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, + {file = "importlib_metadata-4.8.2-py3-none-any.whl", hash = "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100"}, + {file = "importlib_metadata-4.8.2.tar.gz", hash = "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"}, +] +importlib-resources = [ + {file = "importlib_resources-5.2.3-py3-none-any.whl", hash = "sha256:ae35ed1cfe8c0d6c1a53ecd168167f01fa93b893d51a62cdf23aea044c67211b"}, + {file = "importlib_resources-5.2.3.tar.gz", hash = "sha256:203d70dda34cfbfbb42324a8d4211196e7d3e858de21a5eb68c6d1cdd99e4e98"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] ipykernel = [ - {file = "ipykernel-5.5.5-py3-none-any.whl", hash = "sha256:29eee66548ee7c2edb7941de60c0ccf0a7a8dd957341db0a49c5e8e6a0fcb712"}, - {file = "ipykernel-5.5.5.tar.gz", hash = "sha256:e976751336b51082a89fc2099fb7f96ef20f535837c398df6eab1283c2070884"}, + {file = "ipykernel-5.5.6-py3-none-any.whl", hash = "sha256:66f824af1ef4650e1e2f6c42e1423074321440ef79ca3651a6cfd06a4e25e42f"}, + {file = "ipykernel-5.5.6.tar.gz", hash = "sha256:4ea44b90ae1f7c38987ad58ea0809562a17c2695a0499644326f334aecd369ec"}, ] ipython = [ - {file = "ipython-7.16.1-py3-none-any.whl", hash = "sha256:2dbcc8c27ca7d3cfe4fcdff7f45b27f9a8d3edfa70ff8024a71c7a8eb5f09d64"}, - {file = "ipython-7.16.1.tar.gz", hash = "sha256:9f4fcb31d3b2c533333893b9172264e4821c1ac91839500f31bd43f2c59b3ccf"}, + {file = "ipython-7.16.3-py3-none-any.whl", hash = "sha256:c0427ed8bc33ac481faf9d3acf7e84e0010cdaada945e0badd1e2e74cc075833"}, + {file = "ipython-7.16.3.tar.gz", hash = "sha256:5ac47dc9af66fc2f5530c12069390877ae372ac905edca75a92a6e363b5d7caa"}, ] ipython-genutils = [ {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, @@ -1112,24 +1285,24 @@ ipywidgets = [ {file = "ipywidgets-7.6.5.tar.gz", hash = "sha256:00974f7cb4d5f8d494c19810fedb9fa9b64bffd3cda7c2be23c133a1ad3c99c5"}, ] jedi = [ - {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, - {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, + {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"}, + {file = "jedi-0.17.2.tar.gz", hash = "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20"}, ] jinja2 = [ - {file = "Jinja2-3.0.2-py3-none-any.whl", hash = "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"}, - {file = "Jinja2-3.0.2.tar.gz", hash = "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45"}, + {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, + {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, ] jsonschema = [ {file = "jsonschema-4.0.0-py3-none-any.whl", hash = "sha256:c773028c649441ab980015b5b622f4cd5134cf563daaf0235ca4b73cc3734f20"}, {file = "jsonschema-4.0.0.tar.gz", hash = "sha256:bc51325b929171791c42ebc1c70b9713eb134d3bb8ebd5474c8b659b15be6d86"}, ] jupyter-client = [ - {file = "jupyter_client-7.0.6-py3-none-any.whl", hash = "sha256:074bdeb1ffaef4a3095468ee16313938cfdc48fc65ca95cc18980b956c2e5d79"}, - {file = "jupyter_client-7.0.6.tar.gz", hash = "sha256:8b6e06000eb9399775e0a55c52df6c1be4766666209c22f90c2691ded0e338dc"}, + {file = "jupyter_client-7.1.0-py3-none-any.whl", hash = "sha256:64d93752d8cbfba0c1030c3335c3f0d9797cd1efac012652a14aac1653db11a3"}, + {file = "jupyter_client-7.1.0.tar.gz", hash = "sha256:a5f995a73cffb314ed262713ae6dfce53c6b8216cea9f332071b8ff44a6e1654"}, ] jupyter-core = [ - {file = "jupyter_core-4.8.1-py3-none-any.whl", hash = "sha256:8dd262ec8afae95bd512518eb003bc546b76adbf34bf99410e9accdf4be9aa3a"}, - {file = "jupyter_core-4.8.1.tar.gz", hash = "sha256:ef210dcb4fca04de07f2ead4adf408776aca94d17151d6f750ad6ded0b91ea16"}, + {file = "jupyter_core-4.9.1-py3-none-any.whl", hash = "sha256:1c091f3bbefd6f2a8782f2c1db662ca8478ac240e962ae2c66f0b87c818154ea"}, + {file = "jupyter_core-4.9.1.tar.gz", hash = "sha256:dce8a7499da5a53ae3afd5a9f4b02e5df1d57250cf48f3ad79da23b4778cd6fa"}, ] jupyterlab-pygments = [ {file = "jupyterlab_pygments-0.1.2-py2.py3-none-any.whl", hash = "sha256:abfb880fd1561987efaefcb2d2ac75145d2a5d0139b1876d5be806e32f630008"}, @@ -1140,12 +1313,28 @@ jupyterlab-widgets = [ {file = "jupyterlab_widgets-1.0.2.tar.gz", hash = "sha256:7885092b2b96bf189c3a705cc3c412a4472ec5e8382d0b47219a66cccae73cfa"}, ] markupsafe = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -1154,14 +1343,27 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -1171,6 +1373,12 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -1180,37 +1388,34 @@ mistune = [ {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, ] mypy = [ - {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, - {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, - {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, - {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, - {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, - {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, - {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, - {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, - {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, - {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, - {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, - {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, - {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, - {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, - {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, - {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, - {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, - {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, - {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, - {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, - {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, - {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, - {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, + {file = "mypy-0.930-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:221cc94dc6a801ccc2be7c0c9fd791c5e08d1fa2c5e1c12dec4eab15b2469871"}, + {file = "mypy-0.930-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db3a87376a1380f396d465bed462e76ea89f838f4c5e967d68ff6ee34b785c31"}, + {file = "mypy-0.930-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1d2296f35aae9802eeb1327058b550371ee382d71374b3e7d2804035ef0b830b"}, + {file = "mypy-0.930-cp310-cp310-win_amd64.whl", hash = "sha256:959319b9a3cafc33a8185f440a433ba520239c72e733bf91f9efd67b0a8e9b30"}, + {file = "mypy-0.930-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:45a4dc21c789cfd09b8ccafe114d6de66f0b341ad761338de717192f19397a8c"}, + {file = "mypy-0.930-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1e689e92cdebd87607a041585f1dc7339aa2e8a9f9bad9ba7e6ece619431b20c"}, + {file = "mypy-0.930-cp36-cp36m-win_amd64.whl", hash = "sha256:ed4e0ea066bb12f56b2812a15ff223c57c0a44eca817ceb96b214bb055c7051f"}, + {file = "mypy-0.930-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a9d8dffefba634b27d650e0de2564379a1a367e2e08d6617d8f89261a3bf63b2"}, + {file = "mypy-0.930-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b419e9721260161e70d054a15abbd50603c16f159860cfd0daeab647d828fc29"}, + {file = "mypy-0.930-cp37-cp37m-win_amd64.whl", hash = "sha256:601f46593f627f8a9b944f74fd387c9b5f4266b39abad77471947069c2fc7651"}, + {file = "mypy-0.930-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ea7199780c1d7940b82dbc0a4e37722b4e3851264dbba81e01abecc9052d8a7"}, + {file = "mypy-0.930-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:70b197dd8c78fc5d2daf84bd093e8466a2b2e007eedaa85e792e513a820adbf7"}, + {file = "mypy-0.930-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5feb56f8bb280468fe5fc8e6f56f48f99aa0df9eed3c507a11505ee4657b5380"}, + {file = "mypy-0.930-cp38-cp38-win_amd64.whl", hash = "sha256:2e9c5409e9cb81049bb03fa1009b573dea87976713e3898561567a86c4eaee01"}, + {file = "mypy-0.930-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:554873e45c1ca20f31ddf873deb67fa5d2e87b76b97db50669f0468ccded8fae"}, + {file = "mypy-0.930-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0feb82e9fa849affca7edd24713dbe809dce780ced9f3feca5ed3d80e40b777f"}, + {file = "mypy-0.930-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bc1a0607ea03c30225347334af66b0af12eefba018a89a88c209e02b7065ea95"}, + {file = "mypy-0.930-cp39-cp39-win_amd64.whl", hash = "sha256:f9f665d69034b1fcfdbcd4197480d26298bbfb5d2dfe206245b6498addb34999"}, + {file = "mypy-0.930-py3-none-any.whl", hash = "sha256:bf4a44e03040206f7c058d1f5ba02ef2d1820720c88bc4285c7d9a4269f54173"}, + {file = "mypy-0.930.tar.gz", hash = "sha256:51426262ae4714cc7dd5439814676e0992b55bcc0f6514eccb4cf8e0678962c2"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] nbclient = [ - {file = "nbclient-0.5.4-py3-none-any.whl", hash = "sha256:95a300c6fbe73721736cf13972a46d8d666f78794b832866ed7197a504269e11"}, - {file = "nbclient-0.5.4.tar.gz", hash = "sha256:6c8ad36a28edad4562580847f9f1636fe5316a51a323ed85a24a4ad37d4aefce"}, + {file = "nbclient-0.5.9-py3-none-any.whl", hash = "sha256:8a307be4129cce5f70eb83a57c3edbe45656623c31de54e38bb6fdfbadc428b3"}, + {file = "nbclient-0.5.9.tar.gz", hash = "sha256:99e46ddafacd0b861293bf246fed8540a184adfa3aa7d641f89031ec070701e0"}, ] nbconvert = [ {file = "nbconvert-6.0.7-py3-none-any.whl", hash = "sha256:39e9f977920b203baea0be67eea59f7b37a761caa542abe80f5897ce3cf6311d"}, @@ -1221,24 +1426,28 @@ nbformat = [ {file = "nbformat-5.1.3.tar.gz", hash = "sha256:b516788ad70771c6250977c1374fcca6edebe6126fd2adb5a69aa5c2356fd1c8"}, ] nest-asyncio = [ - {file = "nest_asyncio-1.5.1-py3-none-any.whl", hash = "sha256:76d6e972265063fe92a90b9cc4fb82616e07d586b346ed9d2c89a4187acea39c"}, - {file = "nest_asyncio-1.5.1.tar.gz", hash = "sha256:afc5a1c515210a23c461932765691ad39e8eba6551c055ac8d5546e69250d0aa"}, + {file = "nest_asyncio-1.5.4-py3-none-any.whl", hash = "sha256:3fdd0d6061a2bb16f21fe8a9c6a7945be83521d81a0d15cff52e9edee50101d6"}, + {file = "nest_asyncio-1.5.4.tar.gz", hash = "sha256:f969f6013a16fadb4adcf09d11a68a4f617c6049d7af7ac2c676110169a63abd"}, +] +nodeenv = [ + {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, + {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] notebook = [ - {file = "notebook-6.4.4-py3-none-any.whl", hash = "sha256:33488bdcc5cbef23c3cfa12cd51b0b5459a211945b5053d17405980611818149"}, - {file = "notebook-6.4.4.tar.gz", hash = "sha256:26b0095c568e307a310fd78818ad8ebade4f00462dada4c0e34cbad632b9085d"}, + {file = "notebook-6.4.6-py3-none-any.whl", hash = "sha256:5cad068fa82cd4fb98d341c052100ed50cd69fbfb4118cb9b8ab5a346ef27551"}, + {file = "notebook-6.4.6.tar.gz", hash = "sha256:7bcdf79bd1cda534735bd9830d2cbedab4ee34d8fe1df6e7b946b3aab0902ba3"}, ] packaging = [ - {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, - {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pandocfilters = [ {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, ] parso = [ - {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"}, - {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"}, + {file = "parso-0.7.1-py2.py3-none-any.whl", hash = "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea"}, + {file = "parso-0.7.1.tar.gz", hash = "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"}, ] pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, @@ -1260,33 +1469,37 @@ pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] +pre-commit = [ + {file = "pre_commit-2.16.0-py2.py3-none-any.whl", hash = "sha256:758d1dc9b62c2ed8881585c254976d66eae0889919ab9b859064fc2fe3c7743e"}, + {file = "pre_commit-2.16.0.tar.gz", hash = "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65"}, +] prometheus-client = [ - {file = "prometheus_client-0.11.0-py2.py3-none-any.whl", hash = "sha256:b014bc76815eb1399da8ce5fc84b7717a3e63652b0c0f8804092c9363acab1b2"}, - {file = "prometheus_client-0.11.0.tar.gz", hash = "sha256:3a8baade6cb80bcfe43297e33e7623f3118d660d41387593758e2fb1ea173a86"}, + {file = "prometheus_client-0.12.0-py2.py3-none-any.whl", hash = "sha256:317453ebabff0a1b02df7f708efbab21e3489e7072b61cb6957230dd004a0af0"}, + {file = "prometheus_client-0.12.0.tar.gz", hash = "sha256:1b12ba48cee33b9b0b9de64a1047cbd3c5f2d0ab6ebcead7ddda613a750ec3c5"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.20-py3-none-any.whl", hash = "sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c"}, - {file = "prompt_toolkit-3.0.20.tar.gz", hash = "sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"}, + {file = "prompt_toolkit-3.0.24-py3-none-any.whl", hash = "sha256:e56f2ff799bacecd3e88165b1e2f5ebf9bcd59e80e06d395fa0cc4b8bd7bb506"}, + {file = "prompt_toolkit-3.0.24.tar.gz", hash = "sha256:1bb05628c7d87b645974a1bad3f17612be0c29fa39af9f7688030163f680bad6"}, ] ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pycparser = [ - {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, - {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] pygments = [ - {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, - {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, + {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, + {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, + {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, ] pyrsistent = [ {file = "pyrsistent-0.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"}, @@ -1316,31 +1529,67 @@ pytest = [ {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-cov = [ - {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, - {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] pywin32 = [ - {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, - {file = "pywin32-301-cp35-cp35m-win_amd64.whl", hash = "sha256:9635df6998a70282bd36e7ac2a5cef9ead1627b0a63b17c731312c7a0daebb72"}, - {file = "pywin32-301-cp36-cp36m-win32.whl", hash = "sha256:c866f04a182a8cb9b7855de065113bbd2e40524f570db73ef1ee99ff0a5cc2f0"}, - {file = "pywin32-301-cp36-cp36m-win_amd64.whl", hash = "sha256:dafa18e95bf2a92f298fe9c582b0e205aca45c55f989937c52c454ce65b93c78"}, - {file = "pywin32-301-cp37-cp37m-win32.whl", hash = "sha256:98f62a3f60aa64894a290fb7494bfa0bfa0a199e9e052e1ac293b2ad3cd2818b"}, - {file = "pywin32-301-cp37-cp37m-win_amd64.whl", hash = "sha256:fb3b4933e0382ba49305cc6cd3fb18525df7fd96aa434de19ce0878133bf8e4a"}, - {file = "pywin32-301-cp38-cp38-win32.whl", hash = "sha256:88981dd3cfb07432625b180f49bf4e179fb8cbb5704cd512e38dd63636af7a17"}, - {file = "pywin32-301-cp38-cp38-win_amd64.whl", hash = "sha256:8c9d33968aa7fcddf44e47750e18f3d034c3e443a707688a008a2e52bbef7e96"}, - {file = "pywin32-301-cp39-cp39-win32.whl", hash = "sha256:595d397df65f1b2e0beaca63a883ae6d8b6df1cdea85c16ae85f6d2e648133fe"}, - {file = "pywin32-301-cp39-cp39-win_amd64.whl", hash = "sha256:87604a4087434cd814ad8973bd47d6524bd1fa9e971ce428e76b62a5e0860fdf"}, + {file = "pywin32-302-cp310-cp310-win32.whl", hash = "sha256:251b7a9367355ccd1a4cd69cd8dd24bd57b29ad83edb2957cfa30f7ed9941efa"}, + {file = "pywin32-302-cp310-cp310-win_amd64.whl", hash = "sha256:79cf7e6ddaaf1cd47a9e50cc74b5d770801a9db6594464137b1b86aa91edafcc"}, + {file = "pywin32-302-cp36-cp36m-win32.whl", hash = "sha256:fe21c2fb332d03dac29de070f191bdbf14095167f8f2165fdc57db59b1ecc006"}, + {file = "pywin32-302-cp36-cp36m-win_amd64.whl", hash = "sha256:d3761ab4e8c5c2dbc156e2c9ccf38dd51f936dc77e58deb940ffbc4b82a30528"}, + {file = "pywin32-302-cp37-cp37m-win32.whl", hash = "sha256:48dd4e348f1ee9538dd4440bf201ea8c110ea6d9f3a5010d79452e9fa80480d9"}, + {file = "pywin32-302-cp37-cp37m-win_amd64.whl", hash = "sha256:496df89f10c054c9285cc99f9d509e243f4e14ec8dfc6d78c9f0bf147a893ab1"}, + {file = "pywin32-302-cp38-cp38-win32.whl", hash = "sha256:e372e477d938a49266136bff78279ed14445e00718b6c75543334351bf535259"}, + {file = "pywin32-302-cp38-cp38-win_amd64.whl", hash = "sha256:543552e66936378bd2d673c5a0a3d9903dba0b0a87235ef0c584f058ceef5872"}, + {file = "pywin32-302-cp39-cp39-win32.whl", hash = "sha256:2393c1a40dc4497fd6161b76801b8acd727c5610167762b7c3e9fd058ef4a6ab"}, + {file = "pywin32-302-cp39-cp39-win_amd64.whl", hash = "sha256:af5aea18167a31efcacc9f98a2ca932c6b6a6d91ebe31f007509e293dea12580"}, ] pywinpty = [ - {file = "pywinpty-1.1.4-cp36-none-win_amd64.whl", hash = "sha256:fb975976ad92be44801de95fdf2b0366747767cb0528478553aff85dd63ebb09"}, - {file = "pywinpty-1.1.4-cp37-none-win_amd64.whl", hash = "sha256:5d25b30a2f87105778bc2f57cb1271f58aaa25568921ef042faf001b3b0a7307"}, - {file = "pywinpty-1.1.4-cp38-none-win_amd64.whl", hash = "sha256:c5c3550100689632f6663f39865ef8716835dab1838a9eb9b472644af92673f8"}, - {file = "pywinpty-1.1.4-cp39-none-win_amd64.whl", hash = "sha256:ad60a336d92ac38e2159320db6d5999c4c2726a141c3ed3f9694021feb6a234e"}, - {file = "pywinpty-1.1.4.tar.gz", hash = "sha256:cc700c9d5a9fcebf677ac93a4943ca9a24db6e2f11a5f0e7e8e226184c5036f7"}, + {file = "pywinpty-1.1.6-cp310-none-win_amd64.whl", hash = "sha256:5f526f21b569b5610a61e3b6126259c76da979399598e5154498582df3736ade"}, + {file = "pywinpty-1.1.6-cp36-none-win_amd64.whl", hash = "sha256:7576e14f42b31fa98b62d24ded79754d2ea4625570c016b38eb347ce158a30f2"}, + {file = "pywinpty-1.1.6-cp37-none-win_amd64.whl", hash = "sha256:979ffdb9bdbe23db3f46fc7285fd6dbb86b80c12325a50582b211b3894072354"}, + {file = "pywinpty-1.1.6-cp38-none-win_amd64.whl", hash = "sha256:2308b1fc77545427610a705799d4ead5e7f00874af3fb148a03e202437456a7e"}, + {file = "pywinpty-1.1.6-cp39-none-win_amd64.whl", hash = "sha256:c703bf569a98ab7844b9daf37e88ab86f31862754ef6910a8b3824993a525c72"}, + {file = "pywinpty-1.1.6.tar.gz", hash = "sha256:8808f07350c709119cc4464144d6e749637f98e15acc1e5d3c37db1953d2eebc"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] pyzmq = [ {file = "pyzmq-22.3.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:6b217b8f9dfb6628f74b94bdaf9f7408708cb02167d644edca33f38746ca12dd"}, @@ -1348,24 +1597,32 @@ pyzmq = [ {file = "pyzmq-22.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f89468059ebc519a7acde1ee50b779019535db8dcf9b8c162ef669257fef7a93"}, {file = "pyzmq-22.3.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea12133df25e3a6918718fbb9a510c6ee5d3fdd5a346320421aac3882f4feeea"}, {file = "pyzmq-22.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c532fd68b93998aab92356be280deec5de8f8fe59cd28763d2cc8a58747b7f"}, + {file = "pyzmq-22.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f907c7359ce8bf7f7e63c82f75ad0223384105f5126f313400b7e8004d9b33c3"}, + {file = "pyzmq-22.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:902319cfe23366595d3fa769b5b751e6ee6750a0a64c5d9f757d624b2ac3519e"}, {file = "pyzmq-22.3.0-cp310-cp310-win32.whl", hash = "sha256:67db33bea0a29d03e6eeec55a8190e033318cee3cbc732ba8fd939617cbf762d"}, {file = "pyzmq-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:7661fc1d5cb73481cf710a1418a4e1e301ed7d5d924f91c67ba84b2a1b89defd"}, {file = "pyzmq-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79244b9e97948eaf38695f4b8e6fc63b14b78cc37f403c6642ba555517ac1268"}, {file = "pyzmq-22.3.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab888624ed68930442a3f3b0b921ad7439c51ba122dbc8c386e6487a658e4a4e"}, {file = "pyzmq-22.3.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18cd854b423fce44951c3a4d3e686bac8f1243d954f579e120a1714096637cc0"}, {file = "pyzmq-22.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:de8df0684398bd74ad160afdc2a118ca28384ac6f5e234eb0508858d8d2d9364"}, + {file = "pyzmq-22.3.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:62bcade20813796c426409a3e7423862d50ff0639f5a2a95be4b85b09a618666"}, + {file = "pyzmq-22.3.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ea5a79e808baef98c48c884effce05c31a0698c1057de8fc1c688891043c1ce1"}, {file = "pyzmq-22.3.0-cp36-cp36m-win32.whl", hash = "sha256:3c1895c95be92600233e476fe283f042e71cf8f0b938aabf21b7aafa62a8dac9"}, {file = "pyzmq-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:851977788b9caa8ed011f5f643d3ee8653af02c5fc723fa350db5125abf2be7b"}, {file = "pyzmq-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4ebed0977f92320f6686c96e9e8dd29eed199eb8d066936bac991afc37cbb70"}, {file = "pyzmq-22.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42abddebe2c6a35180ca549fadc7228d23c1e1f76167c5ebc8a936b5804ea2df"}, {file = "pyzmq-22.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1e41b32d6f7f9c26bc731a8b529ff592f31fc8b6ef2be9fa74abd05c8a342d7"}, {file = "pyzmq-22.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:be4e0f229cf3a71f9ecd633566bd6f80d9fa6afaaff5489492be63fe459ef98c"}, + {file = "pyzmq-22.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08c4e315a76ef26eb833511ebf3fa87d182152adf43dedee8d79f998a2162a0b"}, + {file = "pyzmq-22.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:badb868fff14cfd0e200eaa845887b1011146a7d26d579aaa7f966c203736b92"}, {file = "pyzmq-22.3.0-cp37-cp37m-win32.whl", hash = "sha256:7c58f598d9fcc52772b89a92d72bf8829c12d09746a6d2c724c5b30076c1f11d"}, {file = "pyzmq-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2b97502c16a5ec611cd52410bdfaab264997c627a46b0f98d3f666227fd1ea2d"}, {file = "pyzmq-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d728b08448e5ac3e4d886b165385a262883c34b84a7fe1166277fe675e1c197a"}, {file = "pyzmq-22.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:480b9931bfb08bf8b094edd4836271d4d6b44150da051547d8c7113bf947a8b0"}, {file = "pyzmq-22.3.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7dc09198e4073e6015d9a8ea093fc348d4e59de49382476940c3dd9ae156fba8"}, {file = "pyzmq-22.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ca6cd58f62a2751728016d40082008d3b3412a7f28ddfb4a2f0d3c130f69e74"}, + {file = "pyzmq-22.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:468bd59a588e276961a918a3060948ae68f6ff5a7fa10bb2f9160c18fe341067"}, + {file = "pyzmq-22.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c88fa7410e9fc471e0858638f403739ee869924dd8e4ae26748496466e27ac59"}, {file = "pyzmq-22.3.0-cp38-cp38-win32.whl", hash = "sha256:c0f84360dcca3481e8674393bdf931f9f10470988f87311b19d23cda869bb6b7"}, {file = "pyzmq-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:f762442bab706fd874064ca218b33a1d8e40d4938e96c24dafd9b12e28017f45"}, {file = "pyzmq-22.3.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:954e73c9cd4d6ae319f1c936ad159072b6d356a92dcbbabfd6e6204b9a79d356"}, @@ -1373,6 +1630,8 @@ pyzmq = [ {file = "pyzmq-22.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:acebba1a23fb9d72b42471c3771b6f2f18dcd46df77482612054bd45c07dfa36"}, {file = "pyzmq-22.3.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cf98fd7a6c8aaa08dbc699ffae33fd71175696d78028281bc7b832b26f00ca57"}, {file = "pyzmq-22.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d072f7dfbdb184f0786d63bda26e8a0882041b1e393fbe98940395f7fab4c5e2"}, + {file = "pyzmq-22.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:53f4fd13976789ffafedd4d46f954c7bb01146121812b72b4ddca286034df966"}, + {file = "pyzmq-22.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1b5d457acbadcf8b27561deeaa386b0217f47626b29672fa7bd31deb6e91e1b"}, {file = "pyzmq-22.3.0-cp39-cp39-win32.whl", hash = "sha256:e6a02cf7271ee94674a44f4e62aa061d2d049001c844657740e156596298b70b"}, {file = "pyzmq-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d3dcb5548ead4f1123851a5ced467791f6986d68c656bc63bfff1bf9e36671e2"}, {file = "pyzmq-22.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3a4c9886d61d386b2b493377d980f502186cd71d501fffdba52bd2a0880cef4f"}, @@ -1381,49 +1640,6 @@ pyzmq = [ {file = "pyzmq-22.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d6157793719de168b199194f6b6173f0ccd3bf3499e6870fac17086072e39115"}, {file = "pyzmq-22.3.0.tar.gz", hash = "sha256:8eddc033e716f8c91c6a2112f0a8ebc5e00532b4a6ae1eb0ccc48e027f9c671c"}, ] -regex = [ - {file = "regex-2021.9.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66696c8336a1b5d1182464f3af3427cc760118f26d0b09a2ddc16a976a4d2637"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d87459ad3ab40cd8493774f8a454b2e490d8e729e7e402a0625867a983e4e02"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78cf6a1e023caf5e9a982f5377414e1aeac55198831b852835732cfd0a0ca5ff"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:255791523f80ea8e48e79af7120b4697ef3b74f6886995dcdb08c41f8e516be0"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e502f8d4e5ef714bcc2c94d499684890c94239526d61fdf1096547db91ca6aa6"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4907fb0f9b9309a5bded72343e675a252c2589a41871874feace9a05a540241e"}, - {file = "regex-2021.9.30-cp310-cp310-win32.whl", hash = "sha256:3be40f720af170a6b20ddd2ad7904c58b13d2b56f6734ee5d09bbdeed2fa4816"}, - {file = "regex-2021.9.30-cp310-cp310-win_amd64.whl", hash = "sha256:c2b180ed30856dfa70cfe927b0fd38e6b68198a03039abdbeb1f2029758d87e7"}, - {file = "regex-2021.9.30-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6f2d2f93001801296fe3ca86515eb04915472b5380d4d8752f09f25f0b9b0ed"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fa7ba9ab2eba7284e0d7d94f61df7af86015b0398e123331362270d71fab0b9"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28040e89a04b60d579c69095c509a4f6a1a5379cd865258e3a186b7105de72c6"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f588209d3e4797882cd238195c175290dbc501973b10a581086b5c6bcd095ffb"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42952d325439ef223e4e9db7ee6d9087b5c68c5c15b1f9de68e990837682fc7b"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cae4099031d80703954c39680323dabd87a69b21262303160776aa0e55970ca0"}, - {file = "regex-2021.9.30-cp36-cp36m-win32.whl", hash = "sha256:0de8ad66b08c3e673b61981b9e3626f8784d5564f8c3928e2ad408c0eb5ac38c"}, - {file = "regex-2021.9.30-cp36-cp36m-win_amd64.whl", hash = "sha256:b345ecde37c86dd7084c62954468a4a655fd2d24fd9b237949dd07a4d0dd6f4c"}, - {file = "regex-2021.9.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6f08187136f11e430638c2c66e1db091105d7c2e9902489f0dbc69b44c222b4"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b55442650f541d195a535ccec33078c78a9521973fb960923da7515e9ed78fa6"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87e9c489aa98f50f367fb26cc9c8908d668e9228d327644d7aa568d47e456f47"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2cb7d4909ed16ed35729d38af585673f1f0833e73dfdf0c18e5be0061107b99"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0861e7f6325e821d5c40514c551fd538b292f8cc3960086e73491b9c5d8291d"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:81fdc90f999b2147fc62e303440c424c47e5573a9b615ed5d43a5b832efcca9e"}, - {file = "regex-2021.9.30-cp37-cp37m-win32.whl", hash = "sha256:8c1ad61fa024195136a6b7b89538030bd00df15f90ac177ca278df9b2386c96f"}, - {file = "regex-2021.9.30-cp37-cp37m-win_amd64.whl", hash = "sha256:e3770781353a4886b68ef10cec31c1f61e8e3a0be5f213c2bb15a86efd999bc4"}, - {file = "regex-2021.9.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9c065d95a514a06b92a5026766d72ac91bfabf581adb5b29bc5c91d4b3ee9b83"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9925985be05d54b3d25fd6c1ea8e50ff1f7c2744c75bdc4d3b45c790afa2bcb3"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470f2c882f2672d8eeda8ab27992aec277c067d280b52541357e1acd7e606dae"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad0517df22a97f1da20d8f1c8cb71a5d1997fa383326b81f9cf22c9dadfbdf34"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e30838df7bfd20db6466fd309d9b580d32855f8e2c2e6d74cf9da27dcd9b63"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b34d2335d6aedec7dcadd3f8283b9682fadad8b9b008da8788d2fce76125ebe"}, - {file = "regex-2021.9.30-cp38-cp38-win32.whl", hash = "sha256:e07049cece3462c626d650e8bf42ddbca3abf4aa08155002c28cb6d9a5a281e2"}, - {file = "regex-2021.9.30-cp38-cp38-win_amd64.whl", hash = "sha256:37868075eda024470bd0feab872c692ac4ee29db1e14baec103257bf6cc64346"}, - {file = "regex-2021.9.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d331f238a7accfbbe1c4cd1ba610d4c087b206353539331e32a8f05345c74aec"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6348a7ab2a502cbdd0b7fd0496d614007489adb7361956b38044d1d588e66e04"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b1cca6c23f19bee8dc40228d9c314d86d1e51996b86f924aca302fc8f8bf9"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f1125bc5172ab3a049bc6f4b9c0aae95a2a2001a77e6d6e4239fa3653e202b5"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:638e98d069b14113e8afba6a54d1ca123f712c0d105e67c1f9211b2a825ef926"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a0b0db6b49da7fa37ca8eddf9f40a8dbc599bad43e64f452284f37b6c34d91c"}, - {file = "regex-2021.9.30-cp39-cp39-win32.whl", hash = "sha256:9910869c472e5a6728680ca357b5846546cbbd2ab3ad5bef986ef0bc438d0aa6"}, - {file = "regex-2021.9.30-cp39-cp39-win_amd64.whl", hash = "sha256:3b71213ec3bad9a5a02e049f2ec86b3d7c3e350129ae0f4e2f99c12b5da919ed"}, - {file = "regex-2021.9.30.tar.gz", hash = "sha256:81e125d9ba54c34579e4539a967e976a3c56150796674aec318b1b2f49251be7"}, -] send2trash = [ {file = "Send2Trash-1.8.0-py3-none-any.whl", hash = "sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08"}, {file = "Send2Trash-1.8.0.tar.gz", hash = "sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d"}, @@ -1445,8 +1661,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"}, - {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"}, + {file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, + {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, ] tornado = [ {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, @@ -1528,13 +1744,16 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] types-dataclasses = [ - {file = "types-dataclasses-0.1.7.tar.gz", hash = "sha256:248075d093d8f7c1541ce515594df7ae40233d1340afde11ce7125368c5209b8"}, - {file = "types_dataclasses-0.1.7-py3-none-any.whl", hash = "sha256:fc372bb68b878ac7a68fd04230d923d4a6303a137ecb0b9700b90630bdfcbfc9"}, + {file = "types-dataclasses-0.6.4.tar.gz", hash = "sha256:2f7ab6c565cf05cc7f27f31a4c2fcc803384e319aab292807b857ddf1473429f"}, + {file = "types_dataclasses-0.6.4-py3-none-any.whl", hash = "sha256:fef6ed4742ca27996530c6d549cd704772a4a86e4781841c9bb387001e369ec3"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, +] +virtualenv = [ + {file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"}, + {file = "virtualenv-20.13.0.tar.gz", hash = "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -1545,8 +1764,8 @@ webencodings = [ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] widgetsnbextension = [ - {file = "widgetsnbextension-3.5.1-py2.py3-none-any.whl", hash = "sha256:bd314f8ceb488571a5ffea6cc5b9fc6cba0adaf88a9d2386b93a489751938bcd"}, - {file = "widgetsnbextension-3.5.1.tar.gz", hash = "sha256:079f87d87270bce047512400efd70238820751a11d2d8cb137a5a5bdbaf255c7"}, + {file = "widgetsnbextension-3.5.2-py2.py3-none-any.whl", hash = "sha256:763a9fdc836d141fa080005a886d63f66f73d56dba1fb5961afc239c77708569"}, + {file = "widgetsnbextension-3.5.2.tar.gz", hash = "sha256:e0731a60ba540cd19bbbefe771a9076dcd2dde90713a8f87f27f53f2d1db7727"}, ] zipp = [ {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, diff --git a/pyproject.toml b/pyproject.toml index a0b8600c8b..4b221c35b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "rich" homepage = "https://github.com/willmcgugan/rich" documentation = "https://rich.readthedocs.io/en/latest/" -version = "10.12.0" +version = "11.2.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" authors = ["Will McGugan "] license = "MIT" @@ -27,7 +27,7 @@ include = ["rich/py.typed"] [tool.poetry.dependencies] python = "^3.6.2" -typing-extensions = { version = "^3.7.4", python = "<3.8" } +typing-extensions = { version = ">=3.7.4, <5.0", python = "<3.8" } dataclasses = { version = ">=0.7,<0.9", python = "<3.7" } pygments = "^2.6.0" commonmark = "^0.9.0" @@ -40,11 +40,12 @@ jupyter = ["ipywidgets"] [tool.poetry.dev-dependencies] pytest = "^6.2.5" -black = "^21.9b0" -mypy = "^0.910" -pytest-cov = "^2.12.1" -attrs = "^21.2.0" -types-dataclasses = "^0.1.7" +black = "^21.11b1" +mypy = "^0.930" +pytest-cov = "^3.0.0" +attrs = "^21.4.0" +types-dataclasses = "^0.6.4" +pre-commit = "^2.16.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/rich/__init__.py b/rich/__init__.py index 604fa04cf5..ed11f5d7ea 100644 --- a/rich/__init__.py +++ b/rich/__init__.py @@ -1,7 +1,7 @@ """Rich text and beautiful formatting in the terminal.""" import os -from typing import IO, TYPE_CHECKING, Any, Optional +from typing import Callable, IO, TYPE_CHECKING, Any, Optional from ._extension import load_ipython_extension @@ -75,6 +75,12 @@ def print_json( data: Any = None, indent: int = 2, highlight: bool = True, + skip_keys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, ) -> None: """Pretty prints JSON. Output will be valid JSON. @@ -83,9 +89,27 @@ def print_json( data (Any): If json is not supplied, then encode this data. indent (int, optional): Number of spaces to indent. Defaults to 2. highlight (bool, optional): Enable highlighting of output: Defaults to True. + skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. + ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. + check_circular (bool, optional): Check for circular references. Defaults to True. + allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. + default (Callable, optional): A callable that converts values that can not be encoded + in to something that can be JSON encoded. Defaults to None. + sort_keys (bool, optional): Sort dictionary keys. Defaults to False. """ - get_console().print_json(json, data=data, indent=indent, highlight=highlight) + get_console().print_json( + json, + data=data, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) def inspect( diff --git a/rich/__main__.py b/rich/__main__.py index 66ae166ba4..ea6ccfd9bd 100644 --- a/rich/__main__.py +++ b/rich/__main__.py @@ -4,13 +4,7 @@ from rich import box from rich.color import Color -from rich.console import ( - Console, - ConsoleOptions, - Group, - RenderResult, - RenderableType, -) +from rich.console import Console, ConsoleOptions, Group, RenderableType, RenderResult from rich.markdown import Markdown from rich.measure import Measurement from rich.pretty import Pretty @@ -222,7 +216,10 @@ def iter_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: test_card = make_test_card() # Print once to warm cache + start = process_time() console.print(test_card) + pre_cache_taken = round((process_time() - start) * 1000.0, 1) + console.file = io.StringIO() start = process_time() @@ -231,10 +228,11 @@ def iter_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: text = console.file.getvalue() # https://bugs.python.org/issue37871 - for line in text.splitlines(): - print(line) + for line in text.splitlines(True): + print(line, end="") - print(f"rendered in {taken}ms") + print(f"rendered in {pre_cache_taken}ms (cold cache)") + print(f"rendered in {taken}ms (warm cache)") from rich.panel import Panel @@ -243,27 +241,25 @@ def iter_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: sponsor_message = Table.grid(padding=1) sponsor_message.add_column(style="green", justify="right") sponsor_message.add_column(no_wrap=True) + sponsor_message.add_row( - "Sponsor me", - "[u blue link=https://github.com/sponsors/willmcgugan]https://github.com/sponsors/willmcgugan", + "Textualize", + "[u blue link=https://github.com/textualize]https://github.com/textualize", ) sponsor_message.add_row( - "Buy me a :coffee:", - "[u blue link=https://ko-fi.com/willmcgugan]https://ko-fi.com/willmcgugan", + "Buy devs a :coffee:", + "[u blue link=https://ko-fi.com/textualize]https://ko-fi.com/textualize", ) sponsor_message.add_row( "Twitter", "[u blue link=https://twitter.com/willmcgugan]https://twitter.com/willmcgugan", ) - sponsor_message.add_row( - "Blog", "[u blue link=https://www.willmcgugan.com]https://www.willmcgugan.com" - ) intro_message = Text.from_markup( """\ -It takes a lot of time to develop Rich and to provide support. +We hope you enjoy using Rich! -Consider supporting my work via Github Sponsors (ask your company / organization), or buy me a coffee to say thanks. +Rich is maintained with [red]:heart:[/] by [link=https://www.textualize.io]Textualize.io[/] - Will McGugan""" ) diff --git a/rich/_inspect.py b/rich/_inspect.py index 262695b1c4..b3652bfbdb 100644 --- a/rich/_inspect.py +++ b/rich/_inspect.py @@ -1,9 +1,10 @@ from __future__ import absolute_import +import inspect from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature from typing import Any, Iterable, Optional, Tuple -from .console import RenderableType, Group +from .console import Group, RenderableType from .highlighter import ReprHighlighter from .jupyter import JupyterMixin from .panel import Panel @@ -106,8 +107,17 @@ def _get_signature(self, name: str, obj: Any) -> Optional[Text]: signature_text = self.highlighter(_signature) qualname = name or getattr(obj, "__qualname__", name) + + # If obj is a module, there may be classes (which are callable) to display + if inspect.isclass(obj): + prefix = "class" + else: + prefix = "def" + qual_signature = Text.assemble( - ("def ", "inspect.def"), (qualname, "inspect.callable"), signature_text + (f"{prefix} ", f"inspect.{prefix}"), + (qualname, "inspect.callable"), + signature_text, ) return qual_signature @@ -204,7 +214,8 @@ def safe_getattr(attr_name: str) -> Tuple[Any, Any]: add_row(key_text, Pretty(value, highlighter=highlighter)) if items_table.row_count: yield items_table - else: + elif not_shown_count: yield Text.from_markup( - f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options." + f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] " + f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options." ) diff --git a/rich/_log_render.py b/rich/_log_render.py index 3a77f0c876..e8810100b3 100644 --- a/rich/_log_render.py +++ b/rich/_log_render.py @@ -75,7 +75,11 @@ def __call__( path, style=f"link file://{link_path}" if link_path else "" ) if line_no: - path_text.append(f":{line_no}") + path_text.append(":") + path_text.append( + f"{line_no}", + style=f"link file://{link_path}#{line_no}" if link_path else "", + ) row.append(path_text) output.add_row(*row) diff --git a/rich/_windows.py b/rich/_windows.py index bac88e639b..b1b30b65ec 100644 --- a/rich/_windows.py +++ b/rich/_windows.py @@ -1,5 +1,4 @@ import sys - from dataclasses import dataclass @@ -15,8 +14,7 @@ class WindowsConsoleFeatures: try: import ctypes - from ctypes import wintypes - from ctypes import LibraryLoader + from ctypes import LibraryLoader, wintypes if sys.platform == "win32": windll = LibraryLoader(ctypes.WinDLL) @@ -30,7 +28,6 @@ def get_windows_console_features() -> WindowsConsoleFeatures: features = WindowsConsoleFeatures() return features - else: STDOUT = -11 diff --git a/rich/box.py b/rich/box.py index de79f05932..d37c6c81c0 100644 --- a/rich/box.py +++ b/rich/box.py @@ -435,7 +435,7 @@ def get_bottom(self, widths: Iterable[int]) -> str: from rich.columns import Columns from rich.panel import Panel - from . import box + from . import box as box from .console import Console from .table import Table from .text import Text diff --git a/rich/cells.py b/rich/cells.py index 7f9c0c16c9..e824ea2a6d 100644 --- a/rich/cells.py +++ b/rich/cells.py @@ -1,9 +1,13 @@ from functools import lru_cache +import re from typing import Dict, List from ._cell_widths import CELL_WIDTHS from ._lru_cache import LRUCache +# Regex to match sequence of the most common character ranges +_is_single_cell_widths = re.compile("^[\u0020-\u006f\u00a0\u02ff\u0370-\u0482]*$").match + def cell_len(text: str, _cache: Dict[str, int] = LRUCache(1024 * 4)) -> int: """Get the number of cells required to display text. @@ -12,16 +16,19 @@ def cell_len(text: str, _cache: Dict[str, int] = LRUCache(1024 * 4)) -> int: text (str): Text to display. Returns: - int: Number of cells required to display the text. + int: Get the number of cells required to display text. """ - cached_result = _cache.get(text, None) - if cached_result is not None: - return cached_result - - _get_size = get_character_cell_size - total_size = sum(_get_size(character) for character in text) - if len(text) <= 64: - _cache[text] = total_size + + if _is_single_cell_widths(text): + return len(text) + else: + cached_result = _cache.get(text, None) + if cached_result is not None: + return cached_result + _get_size = get_character_cell_size + total_size = sum(_get_size(character) for character in text) + if len(text) <= 64: + _cache[text] = total_size return total_size @@ -35,12 +42,10 @@ def get_character_cell_size(character: str) -> int: Returns: int: Number of cells (0, 1 or 2) occupied by that character. """ - - codepoint = ord(character) - if 127 > codepoint > 31: - # Shortcut for ascii + if _is_single_cell_widths(character): return 1 - return _get_codepoint_cell_size(codepoint) + + return _get_codepoint_cell_size(ord(character)) @lru_cache(maxsize=4096) @@ -74,6 +79,15 @@ def _get_codepoint_cell_size(codepoint: int) -> int: def set_cell_size(text: str, total: int) -> str: """Set the length of a string to fit within given number of cells.""" + + if _is_single_cell_widths(text): + size = len(text) + if size < total: + return text + " " * (total - size) + return text[:total] + + if not total: + return "" cell_size = cell_len(text) if cell_size == total: return text @@ -81,12 +95,12 @@ def set_cell_size(text: str, total: int) -> str: return text + " " * (total - cell_size) start = 0 - end = cell_size + end = len(text) # Binary search until we find the right size while True: pos = (start + end) // 2 - before = text[:pos] + before = text[: pos + 1] before_len = cell_len(before) if before_len == total + 1 and cell_len(before[-1]) == 2: return before[:-1] + " " diff --git a/rich/color.py b/rich/color.py index f0fa026d64..b6c56a1ee4 100644 --- a/rich/color.py +++ b/rich/color.py @@ -7,7 +7,7 @@ from ._palettes import EIGHT_BIT_PALETTE, STANDARD_PALETTE, WINDOWS_PALETTE from .color_triplet import ColorTriplet -from .repr import rich_repr, Result +from .repr import Result, rich_repr from .terminal_theme import DEFAULT_TERMINAL_THEME if TYPE_CHECKING: # pragma: no cover @@ -61,6 +61,7 @@ def __repr__(self) -> str: "bright_cyan": 14, "bright_white": 15, "grey0": 16, + "gray0": 16, "navy_blue": 17, "dark_blue": 18, "blue3": 20, @@ -96,6 +97,7 @@ def __repr__(self) -> str: "blue_violet": 57, "orange4": 94, "grey37": 59, + "gray37": 59, "medium_purple4": 60, "slate_blue3": 62, "royal_blue1": 63, @@ -128,7 +130,9 @@ def __repr__(self) -> str: "yellow4": 106, "wheat4": 101, "grey53": 102, + "gray53": 102, "light_slate_grey": 103, + "light_slate_gray": 103, "medium_purple": 104, "light_slate_blue": 105, "dark_olive_green3": 149, @@ -155,11 +159,13 @@ def __repr__(self) -> str: "light_salmon3": 173, "rosy_brown": 138, "grey63": 139, + "gray63": 139, "medium_purple1": 141, "gold3": 178, "dark_khaki": 143, "navajo_white3": 144, "grey69": 145, + "gray69": 145, "light_steel_blue3": 146, "light_steel_blue": 147, "yellow3": 184, @@ -189,6 +195,7 @@ def __repr__(self) -> str: "light_goldenrod2": 222, "light_yellow3": 187, "grey84": 188, + "gray84": 188, "light_steel_blue1": 189, "yellow2": 190, "dark_olive_green1": 192, @@ -223,30 +230,55 @@ def __repr__(self) -> str: "wheat1": 229, "cornsilk1": 230, "grey100": 231, + "gray100": 231, "grey3": 232, + "gray3": 232, "grey7": 233, + "gray7": 233, "grey11": 234, + "gray11": 234, "grey15": 235, + "gray15": 235, "grey19": 236, + "gray19": 236, "grey23": 237, + "gray23": 237, "grey27": 238, + "gray27": 238, "grey30": 239, + "gray30": 239, "grey35": 240, + "gray35": 240, "grey39": 241, + "gray39": 241, "grey42": 242, + "gray42": 242, "grey46": 243, + "gray46": 243, "grey50": 244, + "gray50": 244, "grey54": 245, + "gray54": 245, "grey58": 246, + "gray58": 246, "grey62": 247, + "gray62": 247, "grey66": 248, + "gray66": 248, "grey70": 249, + "gray70": 249, "grey74": 250, + "gray74": 250, "grey78": 251, + "gray78": 251, "grey82": 252, + "gray82": 252, "grey85": 253, + "gray85": 253, "grey89": 254, + "gray89": 254, "grey93": 255, + "gray93": 255, } @@ -279,8 +311,8 @@ class Color(NamedTuple): def __rich__(self) -> "Text": """Dispays the actual color if Rich printed.""" - from .text import Text from .style import Style + from .text import Text return Text.assemble( f" None: diff --git a/rich/console.py b/rich/console.py index d314d9bd8b..fbf87bd5c1 100644 --- a/rich/console.py +++ b/rich/console.py @@ -1,7 +1,6 @@ import inspect import os import platform -import shutil import sys import threading from abc import ABC, abstractmethod @@ -12,8 +11,9 @@ from html import escape from inspect import isclass from itertools import islice +from threading import RLock from time import monotonic -from types import FrameType, TracebackType, ModuleType +from types import FrameType, ModuleType, TracebackType from typing import ( IO, TYPE_CHECKING, @@ -53,6 +53,7 @@ from .measure import Measurement, measure_renderables from .pager import Pager, SystemPager from .pretty import Pretty, is_expandable +from .protocol import rich_cast from .region import Region from .scope import render_scope from .screen import Screen @@ -210,6 +211,19 @@ def update_width(self, width: int) -> "ConsoleOptions": options.min_width = options.max_width = max(0, width) return options + def update_height(self, height: int) -> "ConsoleOptions": + """Update the height, and return a copy. + + Args: + height (int): New height + + Returns: + ~ConsoleOptions: New Console options instance. + """ + options = self.copy() + options.max_height = options.height = height + return options + def update_dimensions(self, width: int, height: int) -> "ConsoleOptions": """Update the width and height, and return a copy. @@ -222,8 +236,7 @@ def update_dimensions(self, width: int, height: int) -> "ConsoleOptions": """ options = self.copy() options.min_width = options.max_width = max(0, width) - options.height = height - options.max_height = height + options.height = options.max_height = height return options @@ -245,11 +258,12 @@ def __rich_console__( ... +# A type that may be rendered by Console. RenderableType = Union[ConsoleRenderable, RichCast, str] -"""A type that may be rendered by Console.""" + +# The result of calling a __rich_console__ method. RenderResult = Iterable[Union[RenderableType, Segment]] -"""The result of calling a __rich_console__ method.""" _null_highlighter = NullHighlighter() @@ -462,9 +476,6 @@ def __rich_console__( yield from self.renderables -RenderGroup = Group # TODO: deprecate at some point - - def group(fit: bool = True) -> Callable[..., Callable[..., Group]]: """A decorator that turns an iterable of renderables in to a group. @@ -475,7 +486,7 @@ def group(fit: bool = True) -> Callable[..., Callable[..., Group]]: def decorator( method: Callable[..., Iterable[RenderableType]] ) -> Callable[..., Group]: - """Convert a method that returns an iterable of renderables in to a RenderGroup.""" + """Convert a method that returns an iterable of renderables in to a Group.""" @wraps(method) def _replace(*args: Any, **kwargs: Any) -> Group: @@ -487,9 +498,6 @@ def _replace(*args: Any, **kwargs: Any) -> Group: return decorator -render_group = group - - def _is_jupyter() -> bool: # pragma: no cover """Check if we're running in a Jupyter notebook.""" try: @@ -650,15 +658,6 @@ def __init__( width = width or 93 height = height or 100 - if width is None: - columns = self._environ.get("COLUMNS") - if columns is not None and columns.isdigit(): - width = int(columns) - if height is None: - lines = self._environ.get("LINES") - if lines is not None and lines.isdigit(): - height = int(lines) - self.soft_wrap = soft_wrap self._width = width self._height = height @@ -666,13 +665,25 @@ def __init__( self.record = record self._markup = markup self._emoji = emoji - self._emoji_variant = emoji_variant + self._emoji_variant: Optional[EmojiVariant] = emoji_variant self._highlight = highlight self.legacy_windows: bool = ( (detect_legacy_windows() and not self.is_jupyter) if legacy_windows is None else legacy_windows ) + if width is None: + columns = self._environ.get("COLUMNS") + if columns is not None and columns.isdigit(): + width = int(columns) - self.legacy_windows + if height is None: + lines = self._environ.get("LINES") + if lines is not None and lines.isdigit(): + height = int(lines) + + self.soft_wrap = soft_wrap + self._width = width + self._height = height self._color_system: Optional[ColorSystem] self._force_terminal = force_terminal @@ -770,7 +781,7 @@ def _detect_color_system(self) -> Optional[ColorSystem]: if color_term in ("truecolor", "24bit"): return ColorSystem.TRUECOLOR term = self._environ.get("TERM", "").strip().lower() - _term_name, _hyphen, colors = term.partition("-") + _term_name, _hyphen, colors = term.rpartition("-") color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD) return color_system @@ -808,12 +819,13 @@ def push_render_hook(self, hook: RenderHook) -> None: Args: hook (RenderHook): Render hook instance. """ - - self._render_hooks.append(hook) + with self._lock: + self._render_hooks.append(hook) def pop_render_hook(self) -> None: """Pop the last renderhook from the stack.""" - self._render_hooks.pop() + with self._lock: + self._render_hooks.pop() def __enter__(self) -> "Console": """Own context manager to enter buffer context.""" @@ -899,7 +911,13 @@ def is_terminal(self) -> bool: if self._force_terminal is not None: return self._force_terminal isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None) - return False if isatty is None else isatty() + try: + return False if isatty is None else isatty() + except ValueError: + # in some situation (at the end of a pytest run for example) isatty() can raise + # ValueError: I/O operation on closed file + # return False because we aren't in a terminal anymore + return False @property def is_dumb_terminal(self) -> bool: @@ -935,15 +953,19 @@ def size(self) -> ConsoleDimensions: """ if self._width is not None and self._height is not None: - return ConsoleDimensions(self._width, self._height) + return ConsoleDimensions(self._width - self.legacy_windows, self._height) if self.is_dumb_terminal: return ConsoleDimensions(80, 25) width: Optional[int] = None height: Optional[int] = None + if WINDOWS: # pragma: no cover - width, height = shutil.get_terminal_size() + try: + width, height = os.get_terminal_size() + except OSError: # Probably not a terminal + pass else: try: width, height = os.get_terminal_size(sys.__stdin__.fileno()) @@ -953,11 +975,18 @@ def size(self) -> ConsoleDimensions: except (AttributeError, ValueError, OSError): pass + columns = self._environ.get("COLUMNS") + if columns is not None and columns.isdigit(): + width = int(columns) + lines = self._environ.get("LINES") + if lines is not None and lines.isdigit(): + height = int(lines) + # get_terminal_size can report 0, 0 if run from pseudo-terminal width = width or 80 height = height or 25 return ConsoleDimensions( - (width - self.legacy_windows) if self._width is None else self._width, + width - self.legacy_windows if self._width is None else self._width, height if self._height is None else self._height, ) @@ -1036,7 +1065,7 @@ def pager( is defined by the system and will typically support at least pressing a key to scroll. Args: - pager (Pager, optional): A pager object, or None to use :class:~rich.pager.SystemPager`. Defaults to None. + pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None. styles (bool, optional): Show styles in pager. Defaults to False. links (bool, optional): Show links in pager. Defaults to False. @@ -1200,8 +1229,8 @@ def render( # No space to render anything. This prevents potential recursion errors. return render_iterable: RenderResult - if hasattr(renderable, "__rich__") and not isclass(renderable): - renderable = renderable.__rich__() # type: ignore + + renderable = rich_cast(renderable) if hasattr(renderable, "__rich_console__") and not isclass(renderable): render_iterable = renderable.__rich_console__(self, _options) # type: ignore elif isinstance(renderable, str): @@ -1419,21 +1448,15 @@ def check_text() -> None: del text[:] for renderable in objects: - # I promise this is sane - # This detects an object which claims to have all attributes, such as MagicMock.mock_calls - if hasattr( - renderable, "jwevpw_eors4dfo6mwo345ermk7kdnfnwerwer" - ): # pragma: no cover - renderable = repr(renderable) - rich_cast = getattr(renderable, "__rich__", None) - if rich_cast: - renderable = rich_cast() + renderable = rich_cast(renderable) if isinstance(renderable, str): append_text( self.render_str( renderable, emoji=emoji, markup=markup, highlighter=_highlighter ) ) + elif isinstance(renderable, Text): + append_text(renderable) elif isinstance(renderable, ConsoleRenderable): check_text() append(renderable) @@ -1479,9 +1502,8 @@ def control(self, *control: Control) -> None: control_codes (str): Control codes, such as those that may move the cursor. """ if not self.is_dumb_terminal: - for _control in control: - self._buffer.append(_control.segment) - self._check_buffer() + with self: + self._buffer.extend(_control.segment for _control in control) def out( self, @@ -1563,7 +1585,7 @@ def print( if overflow is None: overflow = "ignore" crop = False - + render_hooks = self._render_hooks[:] with self: renderables = self._collect_renderables( objects, @@ -1574,7 +1596,7 @@ def print( markup=markup, highlight=highlight, ) - for hook in self._render_hooks: + for hook in render_hooks: renderables = hook.process_renderables(renderables) render_options = self.options.update( justify=justify, @@ -1619,28 +1641,61 @@ def print_json( json: Optional[str] = None, *, data: Any = None, - indent: int = 2, + indent: Union[None, int, str] = 2, highlight: bool = True, + skip_keys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, ) -> None: """Pretty prints JSON. Output will be valid JSON. Args: json (Optional[str]): A string containing JSON. data (Any): If json is not supplied, then encode this data. - indent (int, optional): Number of spaces to indent. Defaults to 2. + indent (Union[None, int, str], optional): Number of spaces to indent. Defaults to 2. highlight (bool, optional): Enable highlighting of output: Defaults to True. + skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. + ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. + check_circular (bool, optional): Check for circular references. Defaults to True. + allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. + default (Callable, optional): A callable that converts values that can not be encoded + in to something that can be JSON encoded. Defaults to None. + sort_keys (bool, optional): Sort dictionary keys. Defaults to False. """ from rich.json import JSON if json is None: - json_renderable = JSON.from_data(data, indent=indent, highlight=highlight) + json_renderable = JSON.from_data( + data, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) else: if not isinstance(json, str): raise TypeError( f"json must be str. Did you mean print_json(data={json!r}) ?" ) - json_renderable = JSON(json, indent=indent, highlight=highlight) - self.print(json_renderable) + json_renderable = JSON( + json, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + self.print(json_renderable, soft_wrap=True) def update_screen( self, @@ -1798,6 +1853,8 @@ def log( if not objects: objects = (NewLine(),) + render_hooks = self._render_hooks[:] + with self: renderables = self._collect_renderables( objects, @@ -1832,7 +1889,7 @@ def log( link_path=link_path, ) ] - for hook in self._render_hooks: + for hook in render_hooks: renderables = hook.process_renderables(renderables) new_segments: List[Segment] = [] extend = new_segments.extend @@ -1913,6 +1970,8 @@ def input( ) -> str: """Displays a prompt and waits for input from the user. The prompt may contain color / style. + It works in the same way as Python's builtin :func:`input` function and provides elaborate line editing and history features if Python's builtin :mod:`readline` module is previously loaded. + Args: prompt (Union[str, Text]): Text to render in the prompt. markup (bool, optional): Enable console markup (requires a str prompt). Defaults to True. diff --git a/rich/default_styles.py b/rich/default_styles.py index 355f9df852..d803eec91d 100644 --- a/rich/default_styles.py +++ b/rich/default_styles.py @@ -2,7 +2,6 @@ from .style import Style - DEFAULT_STYLES: Dict[str, Style] = { "none": Style.null(), "reset": Style( @@ -41,6 +40,7 @@ "inspect.attr.dunder": Style(color="yellow", italic=True, dim=True), "inspect.callable": Style(bold=True, color="red"), "inspect.def": Style(italic=True, color="bright_cyan"), + "inspect.class": Style(italic=True, color="bright_cyan"), "inspect.error": Style(bold=True, color="red"), "inspect.equals": Style(), "inspect.help": Style(color="cyan"), @@ -157,3 +157,27 @@ "markdown.link": Style(color="bright_blue"), "markdown.link_url": Style(color="blue"), } + + +if __name__ == "__main__": # pragma: no cover + import argparse + import io + + from rich.console import Console + from rich.table import Table + from rich.text import Text + + parser = argparse.ArgumentParser() + parser.add_argument("--html", action="store_true", help="Export as HTML table") + args = parser.parse_args() + html: bool = args.html + console = Console(record=True, width=70, file=io.StringIO()) if html else Console() + + table = Table("Name", "Styling") + + for style_name, style in DEFAULT_STYLES.items(): + table.add_row(Text(style_name, style=style), str(style)) + + console.print(table) + if html: + print(console.export_html(inline_styles=True)) diff --git a/rich/diagnose.py b/rich/diagnose.py index 455e11dc02..09c4e0622b 100644 --- a/rich/diagnose.py +++ b/rich/diagnose.py @@ -1,6 +1,35 @@ -if __name__ == "__main__": # pragma: no cover - from rich.console import Console - from rich import inspect +import os +import platform + +from rich import inspect +from rich.console import Console, get_windows_console_features +from rich.panel import Panel +from rich.pretty import Pretty + +def report() -> None: # pragma: no cover + """Print a report to the terminal with debugging information""" console = Console() inspect(console) + features = get_windows_console_features() + inspect(features) + + env_names = ( + "TERM", + "COLORTERM", + "CLICOLOR", + "NO_COLOR", + "TERM_PROGRAM", + "COLUMNS", + "LINES", + "JPY_PARENT_PID", + "VSCODE_VERBOSE_LOGGING", + ) + env = {name: os.getenv(name) for name in env_names} + console.print(Panel.fit((Pretty(env)), title="[b]Environment Variables")) + + console.print(f'platform="{platform.system()}"') + + +if __name__ == "__main__": # pragma: no cover + report() diff --git a/rich/file_proxy.py b/rich/file_proxy.py index 99a6922cb4..3ec593a5a4 100644 --- a/rich/file_proxy.py +++ b/rich/file_proxy.py @@ -44,7 +44,7 @@ def write(self, text: str) -> int: output = Text("\n").join( self.__ansi_decoder.decode_line(line) for line in lines ) - console.print(output, markup=False, emoji=False, highlight=False) + console.print(output) return len(text) def flush(self) -> None: diff --git a/rich/highlighter.py b/rich/highlighter.py index 9e71d00531..8afdd017b6 100644 --- a/rich/highlighter.py +++ b/rich/highlighter.py @@ -96,7 +96,7 @@ class ReprHighlighter(RegexHighlighter): r"(?P\B(\/[\w\.\-\_\+]+)*\/)(?P[\w\.\-\_\+]*)?", r"(?b?\'\'\'.*?(?[a-fA-F0-9]{8}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{12})", - r"(?P(https|http|ws|wss):\/\/[0-9a-zA-Z\$\-\_\+\!`\(\)\,\.\?\/\;\:\&\=\%\#]*)", + r"(?P(file|https|http|ws|wss):\/\/[0-9a-zA-Z\$\-\_\+\!`\(\)\,\.\?\/\;\:\&\=\%\#]*)", ), ] diff --git a/rich/json.py b/rich/json.py index 6266c92ab1..1fc115a952 100644 --- a/rich/json.py +++ b/rich/json.py @@ -1,5 +1,5 @@ from json import loads, dumps -from typing import Any +from typing import Any, Callable, Optional, Union from .text import Text from .highlighter import JSONHighlighter, NullHighlighter @@ -10,30 +10,87 @@ class JSON: Args: json (str): JSON encoded data. - indent (int, optional): Number of characters to indent by. Defaults to 2. + indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2. highlight (bool, optional): Enable highlighting. Defaults to True. + skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. + ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. + check_circular (bool, optional): Check for circular references. Defaults to True. + allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. + default (Callable, optional): A callable that converts values that can not be encoded + in to something that can be JSON encoded. Defaults to None. + sort_keys (bool, optional): Sort dictionary keys. Defaults to False. """ - def __init__(self, json: str, indent: int = 2, highlight: bool = True) -> None: + def __init__( + self, + json: str, + indent: Union[None, int, str] = 2, + highlight: bool = True, + skip_keys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, + ) -> None: data = loads(json) - json = dumps(data, indent=indent) + json = dumps( + data, + indent=indent, + skipkeys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) highlighter = JSONHighlighter() if highlight else NullHighlighter() self.text = highlighter(json) self.text.no_wrap = True self.text.overflow = None @classmethod - def from_data(cls, data: Any, indent: int = 2, highlight: bool = True) -> "JSON": + def from_data( + cls, + data: Any, + indent: Union[None, int, str] = 2, + highlight: bool = True, + skip_keys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, + ) -> "JSON": """Encodes a JSON object from arbitrary data. + Args: + data (Any): An object that may be encoded in to JSON + indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2. + highlight (bool, optional): Enable highlighting. Defaults to True. + default (Callable, optional): Optional callable which will be called for objects that cannot be serialized. Defaults to None. + skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. + ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. + check_circular (bool, optional): Check for circular references. Defaults to True. + allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. + default (Callable, optional): A callable that converts values that can not be encoded + in to something that can be JSON encoded. Defaults to None. + sort_keys (bool, optional): Sort dictionary keys. Defaults to False. + Returns: - Args: - data (Any): An object that may be encoded in to JSON - indent (int, optional): Number of characters to indent by. Defaults to 2. - highlight (bool, optional): Enable highlighting. Defaults to True. + JSON: New JSON object from the given data. """ json_instance: "JSON" = cls.__new__(cls) - json = dumps(data, indent=indent) + json = dumps( + data, + indent=indent, + skipkeys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) highlighter = JSONHighlighter() if highlight else NullHighlighter() json_instance.text = highlighter(json) json_instance.text.no_wrap = True diff --git a/rich/jupyter.py b/rich/jupyter.py index 7cdcc9cabc..bedf5cb19a 100644 --- a/rich/jupyter.py +++ b/rich/jupyter.py @@ -4,7 +4,6 @@ from .segment import Segment from .terminal_theme import DEFAULT_TERMINAL_THEME - JUPYTER_HTML_FORMAT = """\
{code}
""" @@ -75,11 +74,16 @@ def escape(text: str) -> str: def display(segments: Iterable[Segment], text: str) -> None: """Render segments to Jupyter.""" - from IPython.display import display as ipython_display - html = _render_segments(segments) jupyter_renderable = JupyterRenderable(html, text) - ipython_display(jupyter_renderable) + try: + from IPython.display import display as ipython_display + + ipython_display(jupyter_renderable) + except ModuleNotFoundError: + # Handle the case where the Console has force_jupyter=True, + # but IPython is not installed. + pass def print(*args: Any, **kwargs: Any) -> None: diff --git a/rich/live.py b/rich/live.py index f2ba64a7eb..6db5b605f9 100644 --- a/rich/live.py +++ b/rich/live.py @@ -130,35 +130,29 @@ def stop(self) -> None: return self.console.clear_live() self._started = False - try: - if self.auto_refresh and self._refresh_thread is not None: - self._refresh_thread.stop() - # allow it to fully render on the last even if overflow - self.vertical_overflow = "visible" - if not self._alt_screen and not self.console.is_jupyter: - self.refresh() - - finally: - self._disable_redirect_io() - self.console.pop_render_hook() - if not self._alt_screen and self.console.is_terminal: - self.console.line() - self.console.show_cursor(True) - if self._alt_screen: - self.console.set_alt_screen(False) - - if self._refresh_thread is not None: - self._refresh_thread.join() - self._refresh_thread = None - if self.transient and not self._alt_screen: - self.console.control(self._live_render.restore_cursor()) - if self.ipy_widget is not None: # pragma: no cover - if self.transient: - self.ipy_widget.close() - else: - # jupyter last refresh must occur after console pop render hook - # i am not sure why this is needed - self.refresh() + + if self.auto_refresh and self._refresh_thread is not None: + self._refresh_thread.stop() + self._refresh_thread = None + # allow it to fully render on the last even if overflow + self.vertical_overflow = "visible" + with self.console: + try: + if not self._alt_screen and not self.console.is_jupyter: + self.refresh() + finally: + self._disable_redirect_io() + self.console.pop_render_hook() + if not self._alt_screen and self.console.is_terminal: + self.console.line() + self.console.show_cursor(True) + if self._alt_screen: + self.console.set_alt_screen(False) + + if self.transient and not self._alt_screen: + self.console.control(self._live_render.restore_cursor()) + if self.ipy_widget is not None and self.transient: + self.ipy_widget.close() # pragma: no cover def __enter__(self) -> "Live": self.start(refresh=self._renderable is not None) @@ -174,7 +168,7 @@ def __exit__( def _enable_redirect_io(self) -> None: """Enable redirecting of stdout / stderr.""" - if self.console.is_terminal: + if self.console.is_terminal or self.console.is_jupyter: if self._redirect_stdout and not isinstance(sys.stdout, FileProxy): self._restore_stdout = sys.stdout sys.stdout = cast("TextIO", FileProxy(self.console, sys.stdout)) @@ -255,11 +249,7 @@ def process_renderables( if self._alt_screen else self._live_render.position_cursor() ) - renderables = [ - reset, - *renderables, - self._live_render, - ] + renderables = [reset, *renderables, self._live_render] elif ( not self._started and not self.transient ): # if it is finished render the final output for files or dumb_terminals @@ -276,7 +266,7 @@ def process_renderables( from .align import Align from .console import Console - from .live import Live + from .live import Live as Live from .panel import Panel from .rule import Rule from .syntax import Syntax diff --git a/rich/live_render.py b/rich/live_render.py index ca5ad23b2c..f6fa7b2daa 100644 --- a/rich/live_render.py +++ b/rich/live_render.py @@ -84,16 +84,15 @@ def __rich_console__( ) -> RenderResult: renderable = self.renderable - _Segment = Segment style = console.get_style(self.style) lines = console.render_lines(renderable, options, style=style, pad=False) - shape = _Segment.get_shape(lines) + shape = Segment.get_shape(lines) _, height = shape if height > options.size.height: if self.vertical_overflow == "crop": lines = lines[: options.size.height] - shape = _Segment.get_shape(lines) + shape = Segment.get_shape(lines) elif self.vertical_overflow == "ellipsis": lines = lines[: (options.size.height - 1)] overflow_text = Text( @@ -104,10 +103,11 @@ def __rich_console__( style="live.ellipsis", ) lines.append(list(console.render(overflow_text))) - shape = _Segment.get_shape(lines) + shape = Segment.get_shape(lines) self._shape = shape + new_line = Segment.line() for last, line in loop_last(lines): yield from line if not last: - yield _Segment.line() + yield new_line diff --git a/rich/logging.py b/rich/logging.py index c193ee5a2f..002f1f7bf1 100644 --- a/rich/logging.py +++ b/rich/logging.py @@ -150,7 +150,10 @@ def emit(self, record: LogRecord) -> None: log_renderable = self.render( record=record, traceback=traceback, message_renderable=message_renderable ) - self.console.print(log_renderable) + try: + self.console.print(log_renderable) + except Exception: + self.handleError(record) def render_message(self, record: LogRecord, message: str) -> "ConsoleRenderable": """Render message text in to Text. @@ -161,12 +164,13 @@ def render_message(self, record: LogRecord, message: str) -> "ConsoleRenderable" Returns: ConsoleRenderable: Renderable to display log message. """ - use_markup = ( - getattr(record, "markup") if hasattr(record, "markup") else self.markup - ) + use_markup = getattr(record, "markup", self.markup) message_text = Text.from_markup(message) if use_markup else Text(message) - if self.highlighter: - message_text = self.highlighter(message_text) + + highlighter = getattr(record, "highlighter", self.highlighter) + if highlighter: + message_text = highlighter(message_text) + if self.KEYWORDS: message_text.highlight_words(self.KEYWORDS, "logging.keyword") return message_text diff --git a/rich/markdown.py b/rich/markdown.py index 35ac28c1c2..186e103697 100644 --- a/rich/markdown.py +++ b/rich/markdown.py @@ -25,7 +25,7 @@ def create(cls, markdown: "Markdown", node: Any) -> "MarkdownElement": """Factory to create markdown element, Args: - markdown (Markdown): THe parent Markdown object. + markdown (Markdown): The parent Markdown object. node (Any): A node from Pygments. Returns: @@ -177,7 +177,7 @@ def __rich_console__( ) -> RenderResult: code = str(self.text).rstrip() syntax = Panel( - Syntax(code, self.lexer_name, theme=self.theme), + Syntax(code, self.lexer_name, theme=self.theme, word_wrap=True), border_style="dim", box=box.SQUARE, ) @@ -312,7 +312,7 @@ def create(cls, markdown: "Markdown", node: Any) -> "MarkdownElement": """Factory to create markdown element, Args: - markdown (Markdown): THe parent Markdown object. + markdown (Markdown): The parent Markdown object. node (Any): A node from Pygments. Returns: diff --git a/rich/markup.py b/rich/markup.py index 473f86ae1a..58903f6bbb 100644 --- a/rich/markup.py +++ b/rich/markup.py @@ -47,7 +47,7 @@ def markup(self) -> str: def escape( - markup: str, _escape: _EscapeSubMethod = re.compile(r"(\\*)(\[[a-z#\/].*?\])").sub + markup: str, _escape: _EscapeSubMethod = re.compile(r"(\\*)(\[[a-z#\/@].*?\])").sub ) -> str: """Escapes text so that it won't be interpreted as markup. diff --git a/rich/measure.py b/rich/measure.py index 4382184f63..e12787c8be 100644 --- a/rich/measure.py +++ b/rich/measure.py @@ -1,8 +1,8 @@ from operator import itemgetter -from typing import Callable, Iterable, NamedTuple, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, Callable, Iterable, NamedTuple, Optional from . import errors -from .protocol import is_renderable +from .protocol import is_renderable, rich_cast if TYPE_CHECKING: from .console import Console, ConsoleOptions, RenderableType @@ -96,12 +96,13 @@ def get( if _max_width < 1: return Measurement(0, 0) if isinstance(renderable, str): - renderable = console.render_str(renderable, markup=options.markup) - if hasattr(renderable, "__rich__"): - renderable = renderable.__rich__() # type: ignore + renderable = console.render_str( + renderable, markup=options.markup, highlight=False + ) + renderable = rich_cast(renderable) if is_renderable(renderable): - get_console_width: Callable[ - ["Console", "ConsoleOptions"], "Measurement" + get_console_width: Optional[ + Callable[["Console", "ConsoleOptions"], "Measurement"] ] = getattr(renderable, "__rich_measure__", None) if get_console_width is not None: render_width = ( @@ -130,12 +131,12 @@ def measure_renderables( Args: console (~rich.console.Console): Console instance. + options (~rich.console.ConsoleOptions): Console options. renderables (Iterable[RenderableType]): One or more renderable objects. - max_width (int): The maximum width available. Returns: Measurement: Measurement object containing range of character widths required to - contain all given renderables. + contain all given renderables. """ if not renderables: return Measurement(0, 0) diff --git a/rich/padding.py b/rich/padding.py index da1eb3a8d6..1d1f4a553c 100644 --- a/rich/padding.py +++ b/rich/padding.py @@ -89,11 +89,13 @@ def __rich_console__( + self.right, options.max_width, ) + render_options = options.update_width(width - self.left - self.right) + if render_options.height is not None: + render_options = render_options.update_height( + height=render_options.height - self.top - self.bottom + ) lines = console.render_lines( - self.renderable, - options.update_width(width - self.left - self.right), - style=style, - pad=True, + self.renderable, render_options, style=style, pad=True ) _Segment = Segment diff --git a/rich/pager.py b/rich/pager.py index 4290021f21..dbfb973e36 100644 --- a/rich/pager.py +++ b/rich/pager.py @@ -17,9 +17,8 @@ def show(self, content: str) -> None: class SystemPager(Pager): """Uses the pager installed on the system.""" - _pager: Callable[[Any, str], Any] = lambda self, content: __import__("pydoc").pager( - content - ) + def _pager(self, content: str) -> Any: #  pragma: no cover + return __import__("pydoc").pager(content) def show(self, content: str) -> None: """Use the same pager used by pydoc.""" diff --git a/rich/panel.py b/rich/panel.py index fcd1691764..151fe5f017 100644 --- a/rich/panel.py +++ b/rich/panel.py @@ -56,7 +56,7 @@ def __init__( self.renderable = renderable self.box = box self.title = title - self.title_align = title_align + self.title_align: AlignMethod = title_align self.subtitle = subtitle self.subtitle_align = subtitle_align self.safe_box = safe_box diff --git a/rich/pretty.py b/rich/pretty.py index 1a600a5d51..f42434ef5c 100644 --- a/rich/pretty.py +++ b/rich/pretty.py @@ -1,28 +1,29 @@ import builtins +import dataclasses +import inspect import os -from rich.repr import RichReprResult import sys from array import array -from collections import Counter, defaultdict, deque, UserDict, UserList -import dataclasses +from collections import Counter, UserDict, UserList, defaultdict, deque from dataclasses import dataclass, fields, is_dataclass from inspect import isclass from itertools import islice -import re +from types import MappingProxyType from typing import ( - DefaultDict, TYPE_CHECKING, Any, Callable, + DefaultDict, Dict, Iterable, List, Optional, Set, - Union, Tuple, + Union, ) -from types import MappingProxyType + +from rich.repr import RichReprResult try: import attr as _attr_module @@ -30,7 +31,6 @@ _attr_module = None # type: ignore -from .highlighter import ReprHighlighter from . import get_console from ._loop import loop_last from ._pick import pick_bool @@ -51,9 +51,6 @@ RenderResult, ) -# Matches Jupyter's special methods -_re_jupyter_repr = re.compile(f"^_repr_.+_$") - def _is_attr_object(obj: Any) -> bool: """Check if an object was created with attrs module.""" @@ -78,6 +75,80 @@ def _is_dataclass_repr(obj: object) -> bool: # Catching all exceptions in case something is missing on a non CPython implementation try: return obj.__repr__.__code__.co_filename == dataclasses.__file__ + except Exception: # pragma: no coverage + return False + + +def _ipy_display_hook( + value: Any, + console: Optional["Console"] = None, + overflow: "OverflowMethod" = "ignore", + crop: bool = False, + indent_guides: bool = False, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + expand_all: bool = False, +) -> None: + from .console import ConsoleRenderable # needed here to prevent circular import + + # always skip rich generated jupyter renderables or None values + if _safe_isinstance(value, JupyterRenderable) or value is None: + return + + console = console or get_console() + if console.is_jupyter: + # Delegate rendering to IPython if the object (and IPython) supports it + # https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display + ipython_repr_methods = [ + "_repr_html_", + "_repr_markdown_", + "_repr_json_", + "_repr_latex_", + "_repr_jpeg_", + "_repr_png_", + "_repr_svg_", + "_repr_mimebundle_", + ] + for repr_method in ipython_repr_methods: + method = getattr(value, repr_method, None) + if inspect.ismethod(method): + # Calling the method ourselves isn't ideal. The interface for the `_repr_*_` methods + # specifies that if they return None, then they should not be rendered + # by the notebook. + try: + repr_result = method() + except Exception: + continue # If the method raises, treat it as if it doesn't exist, try any others + if repr_result is not None: + return # Delegate rendering to IPython + + # certain renderables should start on a new line + if _safe_isinstance(value, ConsoleRenderable): + console.line() + + console.print( + value + if _safe_isinstance(value, RichRenderable) + else Pretty( + value, + overflow=overflow, + indent_guides=indent_guides, + max_length=max_length, + max_string=max_string, + expand_all=expand_all, + margin=12, + ), + crop=crop, + new_line_start=True, + ) + + +def _safe_isinstance( + obj: object, class_or_tuple: Union[type, Tuple[type, ...]] +) -> bool: + """isinstance can fail in rare cases, for example types with no __class__""" + try: + return isinstance(obj, class_or_tuple) except Exception: return False @@ -106,8 +177,6 @@ def install( """ from rich import get_console - from .console import ConsoleRenderable # needed here to prevent circular import - console = console or get_console() assert console is not None @@ -118,7 +187,7 @@ def display_hook(value: Any) -> None: builtins._ = None # type: ignore console.print( value - if isinstance(value, RichRenderable) + if _safe_isinstance(value, RichRenderable) else Pretty( value, overflow=overflow, @@ -131,44 +200,29 @@ def display_hook(value: Any) -> None: ) builtins._ = value # type: ignore - def ipy_display_hook(value: Any) -> None: # pragma: no cover - assert console is not None - # always skip rich generated jupyter renderables or None values - if isinstance(value, JupyterRenderable) or value is None: - return - # on jupyter rich display, if using one of the special representations don't use rich - if console.is_jupyter and any( - _re_jupyter_repr.match(attr) for attr in dir(value) - ): - return - - # certain renderables should start on a new line - if isinstance(value, ConsoleRenderable): - console.line() - - console.print( - value - if isinstance(value, RichRenderable) - else Pretty( - value, - overflow=overflow, - indent_guides=indent_guides, - max_length=max_length, - max_string=max_string, - expand_all=expand_all, - margin=12, - ), - crop=crop, - new_line_start=True, - ) - try: # pragma: no cover ip = get_ipython() # type: ignore from IPython.core.formatters import BaseFormatter + class RichFormatter(BaseFormatter): # type: ignore + pprint: bool = True + + def __call__(self, value: Any) -> Any: + if self.pprint: + return _ipy_display_hook( + value, + console=get_console(), + overflow=overflow, + indent_guides=indent_guides, + max_length=max_length, + max_string=max_string, + expand_all=expand_all, + ) + else: + return repr(value) + # replace plain text formatter with rich formatter - rich_formatter = BaseFormatter() - rich_formatter.for_type(object, func=ipy_display_hook) + rich_formatter = RichFormatter() ip.display_formatter.formatters["text/plain"] = rich_formatter except Exception: sys.displayhook = display_hook @@ -188,6 +242,7 @@ class Pretty(JupyterMixin): max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. Defaults to None. max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None. + max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None. expand_all (bool, optional): Expand all containers. Defaults to False. margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0. insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False. @@ -205,6 +260,7 @@ def __init__( indent_guides: bool = False, max_length: Optional[int] = None, max_string: Optional[int] = None, + max_depth: Optional[int] = None, expand_all: bool = False, margin: int = 0, insert_line: bool = False, @@ -212,12 +268,13 @@ def __init__( self._object = _object self.highlighter = highlighter or ReprHighlighter() self.indent_size = indent_size - self.justify = justify - self.overflow = overflow + self.justify: Optional["JustifyMethod"] = justify + self.overflow: Optional["OverflowMethod"] = overflow self.no_wrap = no_wrap self.indent_guides = indent_guides self.max_length = max_length self.max_string = max_string + self.max_depth = max_depth self.expand_all = expand_all self.margin = margin self.insert_line = insert_line @@ -231,6 +288,7 @@ def __rich_console__( indent_size=self.indent_size, max_length=self.max_length, max_string=self.max_string, + max_depth=self.max_depth, expand_all=self.expand_all, ) pretty_text = Text( @@ -306,7 +364,7 @@ def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]: def is_expandable(obj: Any) -> bool: """Check if an object may be expanded by pretty print.""" return ( - isinstance(obj, _CONTAINERS) + _safe_isinstance(obj, _CONTAINERS) or (is_dataclass(obj)) or (hasattr(obj, "__rich_repr__")) or _is_attr_object(obj) @@ -466,7 +524,10 @@ def __str__(self) -> str: def traverse( - _object: Any, max_length: Optional[int] = None, max_string: Optional[int] = None + _object: Any, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + max_depth: Optional[int] = None, ) -> Node: """Traverse object and generate a tree. @@ -476,6 +537,8 @@ def traverse( Defaults to None. max_string (int, optional): Maximum length of string before truncating, or None to disable truncating. Defaults to None. + max_depth (int, optional): Maximum depth of data structures, or None for no maximum. + Defaults to None. Returns: Node: The root of a tree structure which can be used to render a pretty repr. @@ -485,7 +548,7 @@ def to_repr(obj: Any) -> str: """Get repr string for an object, but catch errors.""" if ( max_string is not None - and isinstance(obj, (bytes, str)) + and _safe_isinstance(obj, (bytes, str)) and len(obj) > max_string ): truncated = len(obj) - max_string @@ -501,15 +564,17 @@ def to_repr(obj: Any) -> str: push_visited = visited_ids.add pop_visited = visited_ids.remove - def _traverse(obj: Any, root: bool = False) -> Node: + def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node: """Walk the object depth first.""" + obj_type = type(obj) py_version = (sys.version_info.major, sys.version_info.minor) children: List[Node] + reached_max_depth = max_depth is not None and depth >= max_depth def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]: for arg in rich_args: - if isinstance(arg, tuple): + if _safe_isinstance(arg, tuple): if len(arg) == 3: key, child, default = arg if default == child: @@ -546,33 +611,37 @@ def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]: if args: children = [] append = children.append - if angular: - node = Node( - open_brace=f"<{class_name} ", - close_brace=">", - children=children, - last=root, - separator=" ", - ) + + if reached_max_depth: + node = Node(value_repr=f"...") else: - node = Node( - open_brace=f"{class_name}(", - close_brace=")", - children=children, - last=root, - ) - for last, arg in loop_last(args): - if isinstance(arg, tuple): - key, child = arg - child_node = _traverse(child) - child_node.last = last - child_node.key_repr = key - child_node.key_separator = "=" - append(child_node) + if angular: + node = Node( + open_brace=f"<{class_name} ", + close_brace=">", + children=children, + last=root, + separator=" ", + ) else: - child_node = _traverse(arg) - child_node.last = last - append(child_node) + node = Node( + open_brace=f"{class_name}(", + close_brace=")", + children=children, + last=root, + ) + for last, arg in loop_last(args): + if _safe_isinstance(arg, tuple): + key, child = arg + child_node = _traverse(child, depth=depth + 1) + child_node.last = last + child_node.key_repr = key + child_node.key_separator = "=" + append(child_node) + else: + child_node = _traverse(arg, depth=depth + 1) + child_node.last = last + append(child_node) else: node = Node( value_repr=f"<{class_name}>" if angular else f"{class_name}()", @@ -585,40 +654,43 @@ def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]: attr_fields = _get_attr_fields(obj) if attr_fields: - node = Node( - open_brace=f"{obj.__class__.__name__}(", - close_brace=")", - children=children, - last=root, - ) + if reached_max_depth: + node = Node(value_repr=f"...") + else: + node = Node( + open_brace=f"{obj.__class__.__name__}(", + close_brace=")", + children=children, + last=root, + ) - def iter_attrs() -> Iterable[ - Tuple[str, Any, Optional[Callable[[Any], str]]] - ]: - """Iterate over attr fields and values.""" - for attr in attr_fields: - if attr.repr: - try: - value = getattr(obj, attr.name) - except Exception as error: - # Can happen, albeit rarely - yield (attr.name, error, None) - else: - yield ( - attr.name, - value, - attr.repr if callable(attr.repr) else None, - ) - - for last, (name, value, repr_callable) in loop_last(iter_attrs()): - if repr_callable: - child_node = Node(value_repr=str(repr_callable(value))) - else: - child_node = _traverse(value) - child_node.last = last - child_node.key_repr = name - child_node.key_separator = "=" - append(child_node) + def iter_attrs() -> Iterable[ + Tuple[str, Any, Optional[Callable[[Any], str]]] + ]: + """Iterate over attr fields and values.""" + for attr in attr_fields: + if attr.repr: + try: + value = getattr(obj, attr.name) + except Exception as error: + # Can happen, albeit rarely + yield (attr.name, error, None) + else: + yield ( + attr.name, + value, + attr.repr if callable(attr.repr) else None, + ) + + for last, (name, value, repr_callable) in loop_last(iter_attrs()): + if repr_callable: + child_node = Node(value_repr=str(repr_callable(value))) + else: + child_node = _traverse(value, depth=depth + 1) + child_node.last = last + child_node.key_repr = name + child_node.key_separator = "=" + append(child_node) else: node = Node( value_repr=f"{obj.__class__.__name__}()", children=[], last=root @@ -626,7 +698,7 @@ def iter_attrs() -> Iterable[ elif ( is_dataclass(obj) - and not isinstance(obj, type) + and not _safe_isinstance(obj, type) and not fake_attributes and (_is_dataclass_repr(obj) or py_version == (3, 6)) ): @@ -638,26 +710,30 @@ def iter_attrs() -> Iterable[ children = [] append = children.append - node = Node( - open_brace=f"{obj.__class__.__name__}(", - close_brace=")", - children=children, - last=root, - ) + if reached_max_depth: + node = Node(value_repr=f"...") + else: + node = Node( + open_brace=f"{obj.__class__.__name__}(", + close_brace=")", + children=children, + last=root, + ) - for last, field in loop_last(fields(obj)): - if field.repr: - child_node = _traverse(getattr(obj, field.name)) + for last, field in loop_last( + field for field in fields(obj) if field.repr + ): + child_node = _traverse(getattr(obj, field.name), depth=depth + 1) child_node.key_repr = field.name child_node.last = last child_node.key_separator = "=" append(child_node) - pop_visited(obj_id) + pop_visited(obj_id) - elif isinstance(obj, _CONTAINERS): + elif _safe_isinstance(obj, _CONTAINERS): for container_type in _CONTAINERS: - if isinstance(obj, container_type): + if _safe_isinstance(obj, container_type): obj_type = container_type break @@ -669,7 +745,9 @@ def iter_attrs() -> Iterable[ open_brace, close_brace, empty = _BRACES[obj_type](obj) - if obj_type.__repr__ != type(obj).__repr__: + if reached_max_depth: + node = Node(value_repr=f"...", last=root) + elif obj_type.__repr__ != type(obj).__repr__: node = Node(value_repr=to_repr(obj), last=root) elif obj: children = [] @@ -683,12 +761,12 @@ def iter_attrs() -> Iterable[ num_items = len(obj) last_item_index = num_items - 1 - if isinstance(obj, _MAPPING_CONTAINERS): + if _safe_isinstance(obj, _MAPPING_CONTAINERS): iter_items = iter(obj.items()) if max_length is not None: iter_items = islice(iter_items, max_length) for index, (key, child) in enumerate(iter_items): - child_node = _traverse(child) + child_node = _traverse(child, depth=depth + 1) child_node.key_repr = to_repr(key) child_node.last = index == last_item_index append(child_node) @@ -697,7 +775,7 @@ def iter_attrs() -> Iterable[ if max_length is not None: iter_values = islice(iter_values, max_length) for index, child in enumerate(iter_values): - child_node = _traverse(child) + child_node = _traverse(child, depth=depth + 1) child_node.last = index == last_item_index append(child_node) if max_length is not None and num_items > max_length: @@ -708,7 +786,7 @@ def iter_attrs() -> Iterable[ pop_visited(obj_id) else: node = Node(value_repr=to_repr(obj), last=root) - node.is_tuple = isinstance(obj, tuple) + node.is_tuple = _safe_isinstance(obj, tuple) return node node = _traverse(_object, root=True) @@ -722,6 +800,7 @@ def pretty_repr( indent_size: int = 4, max_length: Optional[int] = None, max_string: Optional[int] = None, + max_depth: Optional[int] = None, expand_all: bool = False, ) -> str: """Prettify repr string by expanding on to new lines to fit within a given width. @@ -734,17 +813,21 @@ def pretty_repr( Defaults to None. max_string (int, optional): Maximum length of string before truncating, or None to disable truncating. Defaults to None. + max_depth (int, optional): Maximum depth of nested data structure, or None for no depth. + Defaults to None. expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False. Returns: str: A possibly multi-line representation of the object. """ - if isinstance(_object, Node): + if _safe_isinstance(_object, Node): node = _object else: - node = traverse(_object, max_length=max_length, max_string=max_string) - repr_str = node.render( + node = traverse( + _object, max_length=max_length, max_string=max_string, max_depth=max_depth + ) + repr_str: str = node.render( max_width=max_width, indent_size=indent_size, expand_all=expand_all ) return repr_str @@ -757,6 +840,7 @@ def pprint( indent_guides: bool = True, max_length: Optional[int] = None, max_string: Optional[int] = None, + max_depth: Optional[int] = None, expand_all: bool = False, ) -> None: """A convenience function for pretty printing. @@ -767,6 +851,7 @@ def pprint( max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. Defaults to None. max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None. + max_depth (int, optional): Maximum depth for nested data structures, or None for unlimited depth. Defaults to None. indent_guides (bool, optional): Enable indentation guides. Defaults to True. expand_all (bool, optional): Expand all containers. Defaults to False. """ @@ -776,6 +861,7 @@ def pprint( _object, max_length=max_length, max_string=max_string, + max_depth=max_depth, indent_guides=indent_guides, expand_all=expand_all, overflow="ignore", diff --git a/rich/progress.py b/rich/progress.py index 4eceea7352..115ae81608 100644 --- a/rich/progress.py +++ b/rich/progress.py @@ -271,7 +271,7 @@ def __init__( table_column: Optional[Column] = None, ) -> None: self.text_format = text_format - self.justify = justify + self.justify: JustifyMethod = justify self.style = style self.markup = markup self.highlighter = highlighter @@ -475,7 +475,7 @@ class Task: """Optional[float]: The last speed for a finished task.""" _progress: Deque[ProgressSample] = field( - default_factory=deque, init=False, repr=False + default_factory=lambda: deque(maxlen=1000), init=False, repr=False ) _lock: RLock = field(repr=False, default_factory=RLock) @@ -588,12 +588,7 @@ def __init__( refresh_per_second is None or refresh_per_second > 0 ), "refresh_per_second must be > 0" self._lock = RLock() - self.columns = columns or ( - TextColumn("[progress.description]{task.description}"), - BarColumn(), - TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), - TimeRemainingColumn(), - ) + self.columns = columns or self.get_default_columns() self.speed_estimate_period = speed_estimate_period self.disable = disable @@ -613,6 +608,37 @@ def __init__( self.print = self.console.print self.log = self.console.log + @classmethod + def get_default_columns(cls) -> Tuple[ProgressColumn, ...]: + """Get the default columns used for a new Progress instance: + - a text column for the description (TextColumn) + - the bar itself (BarColumn) + - a text column showing completion percentage (TextColumn) + - an estimated-time-remaining column (TimeRemainingColumn) + If the Progress instance is created without passing a columns argument, + the default columns defined here will be used. + + You can also create a Progress instance using custom columns before + and/or after the defaults, as in this example: + + progress = Progress( + SpinnerColumn(), + *Progress.default_columns(), + "Elapsed:", + TimeElapsedColumn(), + ) + + This code shows the creation of a Progress display, containing + a spinner to the left, the default columns, and a labeled elapsed + time column. + """ + return ( + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), + TimeRemainingColumn(), + ) + @property def console(self) -> Console: return self.live.console @@ -645,6 +671,8 @@ def start(self) -> None: def stop(self) -> None: """Stop the progress display.""" self.live.stop() + if not self.console.is_interactive: + self.console.print() def __enter__(self) -> "Progress": self.start() @@ -764,7 +792,7 @@ def update( task = self._tasks[task_id] completed_start = task.completed - if total is not None: + if total is not None and total != task.total: task.total = total task._reset() if advance is not None: @@ -785,8 +813,6 @@ def update( popleft = _progress.popleft while _progress and _progress[0].timestamp < old_sample_time: popleft() - while len(_progress) > 1000: - popleft() if update_completed > 0: _progress.append(ProgressSample(current_time, update_completed)) if task.completed >= task.total and task.finished_time is None: @@ -1013,10 +1039,7 @@ def remove_task(self, task_id: TaskID) -> None: with Progress( SpinnerColumn(), - TextColumn("[progress.description]{task.description}"), - BarColumn(), - TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), - TimeRemainingColumn(), + *Progress.get_default_columns(), TimeElapsedColumn(), console=console, transient=True, diff --git a/rich/prompt.py b/rich/prompt.py index daf76df855..c56071887a 100644 --- a/rich/prompt.py +++ b/rich/prompt.py @@ -330,11 +330,10 @@ class Confirm(PromptBase[bool]): response_type = bool validate_error_message = "[prompt.invalid]Please enter Y or N" - choices = ["y", "n"] + choices: List[str] = ["y", "n"] def render_default(self, default: DefaultType) -> Text: """Render the default as (y) or (n) rather than True/False.""" - assert self.choices is not None yes, no = self.choices return Text(f"({yes})" if default else f"({no})", style="prompt.default") @@ -343,7 +342,6 @@ def process_response(self, value: str) -> bool: value = value.strip().lower() if value not in self.choices: raise InvalidResponse(self.validate_error_message) - assert self.choices is not None return value == self.choices[0] diff --git a/rich/protocol.py b/rich/protocol.py index 711b31b6a5..ff69cc8b04 100644 --- a/rich/protocol.py +++ b/rich/protocol.py @@ -1,4 +1,10 @@ -from typing import Any +from typing import Any, Callable, cast, Set, TYPE_CHECKING +from inspect import isclass + +if TYPE_CHECKING: + from rich.console import RenderableType + +_GIBBERISH = """aihwerij235234ljsdnp34ksodfipwoe234234jlskjdf""" def is_renderable(check_object: Any) -> bool: @@ -8,3 +14,29 @@ def is_renderable(check_object: Any) -> bool: or hasattr(check_object, "__rich__") or hasattr(check_object, "__rich_console__") ) + + +def rich_cast(renderable: object) -> "RenderableType": + """Cast an object to a renderable by calling __rich__ if present. + + Args: + renderable (object): A potentially renderable object + + Returns: + object: The result of recursively calling __rich__. + """ + from rich.console import RenderableType + + rich_visited_set: Set[type] = set() # Prevent potential infinite loop + while hasattr(renderable, "__rich__") and not isclass(renderable): + # Detect object which claim to have all the attributes + if hasattr(renderable, _GIBBERISH): + return repr(renderable) + cast_method = getattr(renderable, "__rich__") + renderable = cast_method() + renderable_type = type(renderable) + if renderable_type in rich_visited_set: + break + rich_visited_set.add(renderable_type) + + return cast(RenderableType, renderable) diff --git a/rich/segment.py b/rich/segment.py index 97ddc8d0aa..97679cefc0 100644 --- a/rich/segment.py +++ b/rich/segment.py @@ -12,10 +12,16 @@ Optional, Sequence, Tuple, + Type, Union, ) -from .cells import cell_len, get_character_cell_size, set_cell_size +from .cells import ( + _is_single_cell_widths, + cell_len, + get_character_cell_size, + set_cell_size, +) from .repr import Result, rich_repr from .style import Style @@ -97,19 +103,14 @@ def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment text, style, control = segment _Segment = Segment - if cut >= segment.cell_length: - return segment, _Segment("", style, control) - if len(text) == segment.cell_length: - # Fast path with all 1 cell characters - return ( - _Segment(text[:cut], style, control), - _Segment(text[cut:], style, control), - ) + cell_length = segment.cell_length + if cut >= cell_length: + return segment, _Segment("", style, control) cell_size = get_character_cell_size - pos = int((cut / segment.cell_length) * len(text)) + pos = int((cut / cell_length) * len(text)) before = text[:pos] cell_pos = cell_len(before) @@ -143,6 +144,17 @@ def split_cells(self, cut: int) -> Tuple["Segment", "Segment"]: Returns: Tuple[Segment, Segment]: Two segments. """ + text, style, control = self + + if _is_single_cell_widths(text): + # Fast path with all 1 cell characters + if cut >= len(text): + return self, Segment("", style, control) + return ( + Segment(text[:cut], style, control), + Segment(text[cut:], style, control), + ) + return self._split_cells(self, cut) @classmethod @@ -341,7 +353,8 @@ def get_line_length(cls, line: List["Segment"]) -> int: Returns: int: The length of the line. """ - return sum(segment.cell_length for segment in line) + _cell_len = cell_len + return sum(_cell_len(segment.text) for segment in line) @classmethod def get_shape(cls, lines: List[List["Segment"]]) -> Tuple[int, int]: @@ -372,33 +385,116 @@ def set_shape( lines (List[List[Segment]]): A list of lines. width (int): Desired width. height (int, optional): Desired height or None for no change. - style (Style, optional): Style of any padding added. Defaults to None. + style (Style, optional): Style of any padding added. new_lines (bool, optional): Padded lines should include "\n". Defaults to False. Returns: - List[List[Segment]]: New list of lines that fits width x height. + List[List[Segment]]: New list of lines. """ - if height is None: - height = len(lines) - shaped_lines: List[List[Segment]] = [] - pad_line = ( - [Segment(" " * width, style), Segment("\n")] - if new_lines - else [Segment(" " * width, style)] + _height = height or len(lines) + + blank = ( + [cls(" " * width + "\n", style)] if new_lines else [cls(" " * width, style)] ) - append = shaped_lines.append adjust_line_length = cls.adjust_line_length - line: Optional[List[Segment]] - iter_lines = iter(lines) - for _ in range(height): - line = next(iter_lines, None) - if line is None: - append(pad_line) - else: - append(adjust_line_length(line, width, style=style)) + shaped_lines = lines[:_height] + shaped_lines[:] = [ + adjust_line_length(line, width, style=style) for line in lines + ] + if len(shaped_lines) < _height: + shaped_lines.extend([blank] * (_height - len(shaped_lines))) return shaped_lines + @classmethod + def align_top( + cls: Type["Segment"], + lines: List[List["Segment"]], + width: int, + height: int, + style: Style, + new_lines: bool = False, + ) -> List[List["Segment"]]: + """Aligns lines to top (adds extra lines to bottom as required). + + Args: + lines (List[List[Segment]]): A list of lines. + width (int): Desired width. + height (int, optional): Desired height or None for no change. + style (Style): Style of any padding added. + new_lines (bool, optional): Padded lines should include "\n". Defaults to False. + + Returns: + List[List[Segment]]: New list of lines. + """ + extra_lines = height - len(lines) + if not extra_lines: + return lines[:] + lines = lines[:height] + blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) + lines = lines + [[blank]] * extra_lines + return lines + + @classmethod + def align_bottom( + cls: Type["Segment"], + lines: List[List["Segment"]], + width: int, + height: int, + style: Style, + new_lines: bool = False, + ) -> List[List["Segment"]]: + """Aligns render to bottom (adds extra lines above as required). + + Args: + lines (List[List[Segment]]): A list of lines. + width (int): Desired width. + height (int, optional): Desired height or None for no change. + style (Style): Style of any padding added. Defaults to None. + new_lines (bool, optional): Padded lines should include "\n". Defaults to False. + + Returns: + List[List[Segment]]: New list of lines. + """ + extra_lines = height - len(lines) + if not extra_lines: + return lines[:] + lines = lines[:height] + blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) + lines = [[blank]] * extra_lines + lines + return lines + + @classmethod + def align_middle( + cls: Type["Segment"], + lines: List[List["Segment"]], + width: int, + height: int, + style: Style, + new_lines: bool = False, + ) -> List[List["Segment"]]: + """Aligns lines to middle (adds extra lines to above and below as required). + + Args: + lines (List[List[Segment]]): A list of lines. + width (int): Desired width. + height (int, optional): Desired height or None for no change. + style (Style): Style of any padding added. + new_lines (bool, optional): Padded lines should include "\n". Defaults to False. + + Returns: + List[List[Segment]]: New list of lines. + """ + extra_lines = height - len(lines) + if not extra_lines: + return lines[:] + lines = lines[:height] + blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) + top_lines = extra_lines // 2 + bottom_lines = extra_lines - top_lines + lines = [[blank]] * top_lines + lines + [[blank]] * bottom_lines + return lines + @classmethod def simplify(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: """Simplify an iterable of segments by combining contiguous segments with the same style. @@ -492,6 +588,7 @@ def divide( """ split_segments: List["Segment"] = [] add_segment = split_segments.append + iter_cuts = iter(cuts) while True: diff --git a/rich/syntax.py b/rich/syntax.py index 05d41c993e..142f48f46a 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -1,10 +1,10 @@ import os.path import platform -from rich.containers import Lines import textwrap from abc import ABC, abstractmethod from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, Union +from pygments.lexer import Lexer from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename from pygments.style import Style as PygmentsStyle from pygments.styles import get_style_by_name @@ -22,6 +22,8 @@ ) from pygments.util import ClassNotFound +from rich.containers import Lines + from ._loop import loop_first from .color import Color, blend_rgb from .console import Console, ConsoleOptions, JustifyMethod, RenderResult @@ -194,12 +196,13 @@ class Syntax(JupyterMixin): Args: code (str): Code to highlight. - lexer_name (str): Lexer to use (see https://pygments.org/docs/lexers/) + lexer (Lexer | str): Lexer to use (see https://pygments.org/docs/lexers/) theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "monokai". dedent (bool, optional): Enable stripping of initial whitespace. Defaults to False. line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False. start_line (int, optional): Starting number for line numbers. Defaults to 1. - line_range (Tuple[int, int], optional): If given should be a tuple of the start and end line to render. + line_range (Tuple[int | None, int | None], optional): If given should be a tuple of the start and end line to render. + A value of None in the tuple indicates the range is open in that direction. highlight_lines (Set[int]): A set of line numbers to highlight. code_width: Width of code to render (not including line numbers), or ``None`` to use all available width. tab_size (int, optional): Size of tabs. Defaults to 4. @@ -226,13 +229,13 @@ def get_theme(cls, name: Union[str, SyntaxTheme]) -> SyntaxTheme: def __init__( self, code: str, - lexer_name: str, + lexer: Union[Lexer, str], *, theme: Union[str, SyntaxTheme] = DEFAULT_THEME, dedent: bool = False, line_numbers: bool = False, start_line: int = 1, - line_range: Optional[Tuple[int, int]] = None, + line_range: Optional[Tuple[Optional[int], Optional[int]]] = None, highlight_lines: Optional[Set[int]] = None, code_width: Optional[int] = None, tab_size: int = 4, @@ -241,7 +244,7 @@ def __init__( indent_guides: bool = False, ) -> None: self.code = code - self.lexer_name = lexer_name + self._lexer = lexer self.dedent = dedent self.line_numbers = line_numbers self.start_line = start_line @@ -263,6 +266,7 @@ def from_path( cls, path: str, encoding: str = "utf-8", + lexer: Optional[Union[Lexer, str]] = None, theme: Union[str, SyntaxTheme] = DEFAULT_THEME, dedent: bool = False, line_numbers: bool = False, @@ -280,6 +284,7 @@ def from_path( Args: path (str): Path to file to highlight. encoding (str): Encoding of file. + lexer (str | Lexer, optional): Lexer to use. If None, lexer will be auto-detected from path/file content. theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "emacs". dedent (bool, optional): Enable stripping of initial whitespace. Defaults to True. line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False. @@ -298,26 +303,12 @@ def from_path( with open(path, "rt", encoding=encoding) as code_file: code = code_file.read() - lexer = None - lexer_name = "default" - try: - _, ext = os.path.splitext(path) - if ext: - extension = ext.lstrip(".").lower() - lexer = get_lexer_by_name(extension) - lexer_name = lexer.name - except ClassNotFound: - pass - - if lexer is None: - try: - lexer_name = guess_lexer_for_filename(path, code).name - except ClassNotFound: - pass + if not lexer: + lexer = cls.guess_lexer(path, code=code) return cls( code, - lexer_name, + lexer, theme=theme, dedent=dedent, line_numbers=line_numbers, @@ -331,6 +322,48 @@ def from_path( indent_guides=indent_guides, ) + @classmethod + def guess_lexer(cls, path: str, code: Optional[str] = None) -> str: + """Guess the alias of the Pygments lexer to use based on a path and an optional string of code. + If code is supplied, it will use a combination of the code and the filename to determine the + best lexer to use. For example, if the file is ``index.html`` and the file contains Django + templating syntax, then "html+django" will be returned. If the file is ``index.html``, and no + templating language is used, the "html" lexer will be used. If no string of code + is supplied, the lexer will be chosen based on the file extension.. + + Args: + path (AnyStr): The path to the file containing the code you wish to know the lexer for. + code (str, optional): Optional string of code that will be used as a fallback if no lexer + is found for the supplied path. + + Returns: + str: The name of the Pygments lexer that best matches the supplied path/code. + """ + lexer: Optional[Lexer] = None + lexer_name = "default" + if code: + try: + lexer = guess_lexer_for_filename(path, code) + except ClassNotFound: + pass + + if not lexer: + try: + _, ext = os.path.splitext(path) + if ext: + extension = ext.lstrip(".").lower() + lexer = get_lexer_by_name(extension) + except ClassNotFound: + pass + + if lexer: + if lexer.aliases: + lexer_name = lexer.aliases[0] + else: + lexer_name = lexer.name + + return lexer_name + def _get_base_style(self) -> Style: """Get the base style.""" default_style = self._theme.get_background_style() + self.background_style @@ -348,8 +381,29 @@ def _get_token_color(self, token_type: TokenType) -> Optional[Color]: style = self._theme.get_style_for_token(token_type) return style.color + @property + def lexer(self) -> Optional[Lexer]: + """The lexer for this syntax, or None if no lexer was found. + + Tries to find the lexer by name if a string was passed to the constructor. + """ + + if isinstance(self._lexer, Lexer): + return self._lexer + try: + return get_lexer_by_name( + self._lexer, + stripnl=False, + ensurenl=True, + tabsize=self.tab_size, + ) + except ClassNotFound: + return None + def highlight( - self, code: str, line_range: Optional[Tuple[int, int]] = None + self, + code: str, + line_range: Optional[Tuple[Optional[int], Optional[int]]] = None, ) -> Text: """Highlight code and return a Text instance. @@ -373,14 +427,10 @@ def highlight( no_wrap=not self.word_wrap, ) _get_theme_style = self._theme.get_style_for_token - try: - lexer = get_lexer_by_name( - self.lexer_name, - stripnl=False, - ensurenl=True, - tabsize=self.tab_size, - ) - except ClassNotFound: + + lexer = self.lexer + + if lexer is None: text.append(code) else: if line_range: @@ -390,6 +440,8 @@ def highlight( def line_tokenize() -> Iterable[Tuple[Any, str]]: """Split tokens to one per line.""" + assert lexer + for token_type, token in lexer.get_tokens(code): while token: line_token, new_line, token = token.partition("\n") @@ -399,7 +451,7 @@ def tokens_to_spans() -> Iterable[Tuple[str, Optional[Style]]]: """Convert tokens to spans.""" tokens = iter(line_tokenize()) line_no = 0 - _line_start = line_start - 1 + _line_start = line_start - 1 if line_start else 0 # Skip over tokens until line start while line_no < _line_start: @@ -412,7 +464,7 @@ def tokens_to_spans() -> Iterable[Tuple[str, Optional[Style]]]: yield (token, _get_theme_style(token_type)) if token.endswith("\n"): line_no += 1 - if line_no >= line_end: + if line_end and line_no >= line_end: break text.append_tokens(tokens_to_spans()) @@ -495,11 +547,6 @@ def __rich_console__( else self.code_width ) - line_offset = 0 - if self.line_range: - start_line, end_line = self.line_range - line_offset = max(0, start_line - 1) - ends_on_nl = self.code.endswith("\n") code = self.code if ends_on_nl else self.code + "\n" code = textwrap.dedent(code) if self.dedent else code @@ -532,7 +579,7 @@ def __rich_console__( else: syntax_lines = console.render_lines( text, - options.update(width=code_width, height=None), + options.update(width=code_width, height=None, justify="left"), style=self.background_style, pad=True, new_lines=True, @@ -541,6 +588,10 @@ def __rich_console__( yield from syntax_line return + start_line, end_line = self.line_range or (None, None) + line_offset = 0 + if start_line: + line_offset = max(0, start_line - 1) lines: Union[List[Text], Lines] = text.split("\n", allow_blank=ends_on_nl) if self.line_range: lines = lines[line_offset:end_line] @@ -573,7 +624,7 @@ def __rich_console__( if self.word_wrap: wrapped_lines = console.render_lines( line, - render_options.update(height=None), + render_options.update(height=None, justify="left"), style=background_style, pad=not transparent_background, ) @@ -698,7 +749,7 @@ def __rich_console__( code = sys.stdin.read() syntax = Syntax( code=code, - lexer_name=args.lexer_name, + lexer=args.lexer_name, line_numbers=args.line_numbers, word_wrap=args.word_wrap, theme=args.theme, @@ -708,6 +759,7 @@ def __rich_console__( else: syntax = Syntax.from_path( args.path, + lexer=args.lexer_name, line_numbers=args.line_numbers, word_wrap=args.word_wrap, theme=args.theme, diff --git a/rich/table.py b/rich/table.py index b6a9c1ae6e..0271d5fc66 100644 --- a/rich/table.py +++ b/rich/table.py @@ -1,11 +1,12 @@ from dataclasses import dataclass, field, replace from typing import ( - Dict, TYPE_CHECKING, + Dict, Iterable, List, NamedTuple, Optional, + Sequence, Tuple, Union, ) @@ -14,6 +15,7 @@ from ._loop import loop_first_last, loop_last from ._pick import pick_bool from ._ratio import ratio_distribute, ratio_reduce +from .align import VerticalAlignMethod from .jupyter import JupyterMixin from .measure import Measurement from .padding import Padding, PaddingDimensions @@ -55,6 +57,9 @@ class Column: justify: "JustifyMethod" = "left" """str: How to justify text within the column ("left", "center", "right", or "full")""" + vertical: "VerticalAlignMethod" = "top" + """str: How to vertically align content ("top", "middle", or "bottom")""" + overflow: "OverflowMethod" = "ellipsis" """str: Overflow method.""" @@ -111,6 +116,8 @@ class _Cell(NamedTuple): """Style to apply to cell.""" renderable: "RenderableType" """Cell renderable.""" + vertical: VerticalAlignMethod + """Cell vertical alignment.""" class Table(JupyterMixin): @@ -201,10 +208,10 @@ def __init__( self.border_style = border_style self.title_style = title_style self.caption_style = caption_style - self.title_justify = title_justify - self.caption_justify = caption_justify + self.title_justify: "JustifyMethod" = title_justify + self.caption_justify: "JustifyMethod" = caption_justify self.highlight = highlight - self.row_styles = list(row_styles or []) + self.row_styles: Sequence[StyleType] = list(row_styles or []) append_column = self.columns.append for header in headers: if isinstance(header, str): @@ -247,7 +254,7 @@ def grid( ) @property - def expand(self) -> int: + def expand(self) -> bool: """Setting a non-None self.width implies expand.""" return self._expand or self.width is not None @@ -334,6 +341,7 @@ def add_column( footer_style: Optional[StyleType] = None, style: Optional[StyleType] = None, justify: "JustifyMethod" = "left", + vertical: "VerticalAlignMethod" = "top", overflow: "OverflowMethod" = "ellipsis", width: Optional[int] = None, min_width: Optional[int] = None, @@ -352,6 +360,7 @@ def add_column( footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None. style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None. justify (JustifyMethod, optional): Alignment for cells. Defaults to "left". + vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top". overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis". width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None. min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None. @@ -368,6 +377,7 @@ def add_column( footer_style=footer_style or "", style=style or "", justify=justify, + vertical=vertical, overflow=overflow, width=width, min_width=min_width, @@ -635,10 +645,18 @@ def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]: if any_padding: _Padding = Padding for first, last, (style, renderable) in loop_first_last(raw_cells): - yield _Cell(style, _Padding(renderable, get_padding(first, last))) + yield _Cell( + style, + _Padding(renderable, get_padding(first, last)), + getattr(renderable, "vertical", None) or column.vertical, + ) else: for (style, renderable) in raw_cells: - yield _Cell(style, renderable) + yield _Cell( + style, + renderable, + getattr(renderable, "vertical", None) or column.vertical, + ) def _get_padding_width(self, column_index: int) -> int: """Get extra width from padding.""" @@ -769,18 +787,45 @@ def _render( overflow=column.overflow, height=None, ) - cell_style = table_style + row_style + get_style(cell.style) lines = console.render_lines( - cell.renderable, render_options, style=cell_style + cell.renderable, + render_options, + style=get_style(cell.style) + row_style, ) max_height = max(max_height, len(lines)) cells.append(lines) + row_height = max(len(cell) for cell in cells) + + def align_cell( + cell: List[List[Segment]], + vertical: "VerticalAlignMethod", + width: int, + style: Style, + ) -> List[List[Segment]]: + if header_row: + vertical = "bottom" + elif footer_row: + vertical = "top" + + if vertical == "top": + return _Segment.align_top(cell, width, row_height, style) + elif vertical == "middle": + return _Segment.align_middle(cell, width, row_height, style) + return _Segment.align_bottom(cell, width, row_height, style) + cells[:] = [ _Segment.set_shape( - _cell, width, max_height, style=table_style + row_style + align_cell( + cell, + _cell.vertical, + width, + get_style(_cell.style) + row_style, + ), + width, + max_height, ) - for width, _cell in zip(widths, cells) + for width, _cell, cell, column in zip(widths, row_cell, cells, columns) ] if _box: @@ -845,74 +890,79 @@ def _render( if __name__ == "__main__": # pragma: no cover from rich.console import Console from rich.highlighter import ReprHighlighter - from rich.table import Table + from rich.table import Table as Table - table = Table( - title="Star Wars Movies", - caption="Rich example table", - caption_justify="right", - ) + from ._timer import timer + + with timer("Table render"): + table = Table( + title="Star Wars Movies", + caption="Rich example table", + caption_justify="right", + ) - table.add_column("Released", header_style="bright_cyan", style="cyan", no_wrap=True) - table.add_column("Title", style="magenta") - table.add_column("Box Office", justify="right", style="green") + table.add_column( + "Released", header_style="bright_cyan", style="cyan", no_wrap=True + ) + table.add_column("Title", style="magenta") + table.add_column("Box Office", justify="right", style="green") - table.add_row( - "Dec 20, 2019", - "Star Wars: The Rise of Skywalker", - "$952,110,690", - ) - table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") - table.add_row( - "Dec 15, 2017", - "Star Wars Ep. V111: The Last Jedi", - "$1,332,539,889", - style="on black", - end_section=True, - ) - table.add_row( - "Dec 16, 2016", - "Rogue One: A Star Wars Story", - "$1,332,439,889", - ) + table.add_row( + "Dec 20, 2019", + "Star Wars: The Rise of Skywalker", + "$952,110,690", + ) + table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") + table.add_row( + "Dec 15, 2017", + "Star Wars Ep. V111: The Last Jedi", + "$1,332,539,889", + style="on black", + end_section=True, + ) + table.add_row( + "Dec 16, 2016", + "Rogue One: A Star Wars Story", + "$1,332,439,889", + ) - def header(text: str) -> None: - console.print() - console.rule(highlight(text)) - console.print() - - console = Console() - highlight = ReprHighlighter() - header("Example Table") - console.print(table, justify="center") - - table.expand = True - header("expand=True") - console.print(table) - - table.width = 50 - header("width=50") - - console.print(table, justify="center") - - table.width = None - table.expand = False - table.row_styles = ["dim", "none"] - header("row_styles=['dim', 'none']") - - console.print(table, justify="center") - - table.width = None - table.expand = False - table.row_styles = ["dim", "none"] - table.leading = 1 - header("leading=1, row_styles=['dim', 'none']") - console.print(table, justify="center") - - table.width = None - table.expand = False - table.row_styles = ["dim", "none"] - table.show_lines = True - table.leading = 0 - header("show_lines=True, row_styles=['dim', 'none']") - console.print(table, justify="center") + def header(text: str) -> None: + console.print() + console.rule(highlight(text)) + console.print() + + console = Console() + highlight = ReprHighlighter() + header("Example Table") + console.print(table, justify="center") + + table.expand = True + header("expand=True") + console.print(table) + + table.width = 50 + header("width=50") + + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + header("row_styles=['dim', 'none']") + + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + table.leading = 1 + header("leading=1, row_styles=['dim', 'none']") + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + table.show_lines = True + table.leading = 0 + header("show_lines=True, row_styles=['dim', 'none']") + console.print(table, justify="center") diff --git a/rich/text.py b/rich/text.py index cce93274e3..d9ac2c0d6d 100644 --- a/rich/text.py +++ b/rich/text.py @@ -1,7 +1,7 @@ import re from functools import partial, reduce from math import gcd -from operator import attrgetter, itemgetter +from operator import itemgetter from rich.emoji import EmojiVariant from typing import ( TYPE_CHECKING, @@ -14,7 +14,6 @@ Optional, Tuple, Union, - cast, ) from ._loop import loop_last @@ -58,7 +57,7 @@ def __repr__(self) -> str: return ( f"Span({self.start}, {self.end}, {self.style!r})" if (isinstance(self.style, Style) and self.style._meta) - else f"Span({self.start}, {self.end}, {str(self.style)!r})" + else f"Span({self.start}, {self.end}, {repr(self.style)})" ) def __bool__(self) -> bool: @@ -144,8 +143,8 @@ def __init__( ) -> None: self._text = [strip_control_codes(text)] self.style = style - self.justify = justify - self.overflow = overflow + self.justify: Optional["JustifyMethod"] = justify + self.overflow: Optional["OverflowMethod"] = overflow self.no_wrap = no_wrap self.end = end self.tab_size = tab_size @@ -214,6 +213,36 @@ def cell_len(self) -> int: """Get the number of cells required to render this text.""" return cell_len(self.plain) + @property + def markup(self) -> str: + """Get console markup to render this Text. + + Returns: + str: A string potentially creating markup tags. + """ + from .markup import escape + + output: List[str] = [] + + plain = self.plain + markup_spans = [ + (0, False, self.style), + *((span.start, False, span.style) for span in self._spans), + *((span.end, True, span.style) for span in self._spans), + (len(plain), True, self.style), + ] + markup_spans.sort(key=itemgetter(0, 1)) + position = 0 + append = output.append + for offset, closing, style in markup_spans: + if offset > position: + append(escape(plain[position:offset])) + position = offset + if style: + append(f"[/{style}]" if closing else f"[{style}]") + markup = "".join(output) + return markup + @classmethod def from_markup( cls, @@ -243,6 +272,44 @@ def from_markup( rendered_text.overflow = overflow return rendered_text + @classmethod + def from_ansi( + cls, + text: str, + *, + style: Union[str, Style] = "", + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + no_wrap: Optional[bool] = None, + end: str = "\n", + tab_size: Optional[int] = 8, + ) -> "Text": + """Create a Text object from a string containing ANSI escape codes. + + Args: + text (str): A string containing escape codes. + style (Union[str, Style], optional): Base style for text. Defaults to "". + justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. + overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. + no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None. + end (str, optional): Character to end text with. Defaults to "\\\\n". + tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to 8. + """ + from .ansi import AnsiDecoder + + joiner = Text( + "\n", + justify=justify, + overflow=overflow, + no_wrap=no_wrap, + end=end, + tab_size=tab_size, + style=style, + ) + decoder = AnsiDecoder() + result = joiner.join(line for line in decoder.decode(text)) + return result + @classmethod def styled( cls, @@ -555,15 +622,9 @@ def __rich_console__( self, console: "Console", options: "ConsoleOptions" ) -> Iterable[Segment]: tab_size: int = console.tab_size or self.tab_size or 8 - justify = ( - cast("JustifyMethod", self.justify) or options.justify or DEFAULT_OVERFLOW - ) + justify = self.justify or options.justify or DEFAULT_JUSTIFY - overflow = ( - cast("OverflowMethod", self.overflow) - or options.overflow - or DEFAULT_OVERFLOW - ) + overflow = self.overflow or options.overflow or DEFAULT_OVERFLOW lines = self.wrap( console, @@ -972,6 +1033,7 @@ def divide(self, offsets: Iterable[int]) -> Lines: Lines: New RichText instances between offsets. """ _offsets = list(offsets) + if not _offsets: return Lines([self.copy()]) @@ -995,33 +1057,49 @@ def divide(self, offsets: Iterable[int]) -> Lines: ) if not self._spans: return new_lines - order = {span: span_index for span_index, span in enumerate(self._spans)} - span_stack = sorted(self._spans, key=attrgetter("start"), reverse=True) - pop = span_stack.pop - push = span_stack.append + _line_appends = [line._spans.append for line in new_lines._lines] + line_count = len(line_ranges) _Span = Span - get_order = order.__getitem__ - - for line, (start, end) in zip(new_lines, line_ranges): - if not span_stack: - break - append_span = line._spans.append - position = len(span_stack) - 1 - while span_stack[position].start < end: - span = pop(position) - add_span, remaining_span = span.split(end) - if remaining_span: - push(remaining_span) - order[remaining_span] = order[span] - span_start, span_end, span_style = add_span - line_span = _Span(span_start - start, span_end - start, span_style) - order[line_span] = order[span] - append_span(line_span) - position -= 1 - if position < 0 or not span_stack: - break # pragma: no cover - line._spans.sort(key=get_order) + + for span_start, span_end, style in self._spans: + + lower_bound = 0 + upper_bound = line_count + start_line_no = (lower_bound + upper_bound) // 2 + + while True: + line_start, line_end = line_ranges[start_line_no] + if span_start < line_start: + upper_bound = start_line_no - 1 + elif span_start > line_end: + lower_bound = start_line_no + 1 + else: + break + start_line_no = (lower_bound + upper_bound) // 2 + + if span_end < line_end: + end_line_no = start_line_no + else: + end_line_no = lower_bound = start_line_no + upper_bound = line_count + + while True: + line_start, line_end = line_ranges[end_line_no] + if span_end < line_start: + upper_bound = end_line_no - 1 + elif span_end > line_end: + lower_bound = end_line_no + 1 + else: + break + end_line_no = (lower_bound + upper_bound) // 2 + + for line_no in range(start_line_no, end_line_no + 1): + line_start, line_end = line_ranges[line_no] + new_start = max(0, span_start - line_start) + new_end = min(span_end - line_start, line_end - line_start) + if new_end > new_start: + _line_appends[line_no](_Span(new_start, new_end, style)) return new_lines @@ -1065,10 +1143,8 @@ def wrap( Returns: Lines: Number of lines. """ - wrap_justify = cast("JustifyMethod", justify or self.justify) or DEFAULT_JUSTIFY - wrap_overflow = ( - cast("OverflowMethod", overflow or self.overflow) or DEFAULT_OVERFLOW - ) + wrap_justify = justify or self.justify or DEFAULT_JUSTIFY + wrap_overflow = overflow or self.overflow or DEFAULT_OVERFLOW no_wrap = pick_bool(no_wrap, self.no_wrap, False) or overflow == "ignore" @@ -1188,6 +1264,7 @@ def with_indent_guides( text.highlight_words(["ipsum"], "italic") console = Console() + console.rule("justify='left'") console.print(text, style="red") console.print() diff --git a/rich/traceback.py b/rich/traceback.py index 4315fe0539..1d3b71ea32 100644 --- a/rich/traceback.py +++ b/rich/traceback.py @@ -16,13 +16,7 @@ from . import pretty from ._loop import loop_first, loop_last from .columns import Columns -from .console import ( - Console, - ConsoleOptions, - ConsoleRenderable, - RenderResult, - group, -) +from .console import Console, ConsoleOptions, ConsoleRenderable, RenderResult, group from .constrain import Constrain from .highlighter import RegexHighlighter, ReprHighlighter from .panel import Panel @@ -135,15 +129,15 @@ def ipy_display_traceback( ) try: # pragma: no cover - # if wihin ipython, use customized traceback + # if within ipython, use customized traceback ip = get_ipython() # type: ignore ipy_excepthook_closure(ip) - return sys.excepthook # type: ignore # more strict signature that mypy can't interpret + return sys.excepthook except Exception: # otherwise use default system hook old_excepthook = sys.excepthook sys.excepthook = excepthook - return old_excepthook # type: ignore # more strict signature that mypy can't interpret + return old_excepthook @dataclass @@ -246,6 +240,9 @@ def __init__( self.suppress: Sequence[str] = [] for suppress_entity in suppress: if not isinstance(suppress_entity, str): + assert ( + suppress_entity.__file__ is not None + ), f"{suppress_entity!r} must be a module with '__file__' attribute" path = os.path.dirname(suppress_entity.__file__) else: path = suppress_entity @@ -440,7 +437,8 @@ def __rich_console__( "scope.equals": token_style(Operator), "scope.key": token_style(Name), "scope.key.special": token_style(Name.Constant) + Style(dim=True), - } + }, + inherit=False, ) highlighter = ReprHighlighter() @@ -450,7 +448,7 @@ def __rich_console__( self._render_stack(stack), title="[traceback.title]Traceback [dim](most recent call last)", style=background_style, - border_style="traceback.border.syntax_error", + border_style="traceback.border", expand=True, padding=(0, 1), ) @@ -463,7 +461,7 @@ def __rich_console__( Panel( self._render_syntax_error(stack.syntax_error), style=background_style, - border_style="traceback.border", + border_style="traceback.border.syntax_error", expand=True, padding=(0, 1), width=self.width, diff --git a/rich/tree.py b/rich/tree.py index 5fd46fd46b..66203e693e 100644 --- a/rich/tree.py +++ b/rich/tree.py @@ -28,6 +28,7 @@ def __init__( guide_style: StyleType = "tree.line", expanded: bool = True, highlight: bool = False, + hide_root: bool = False, ) -> None: self.label = label self.style = style @@ -35,6 +36,7 @@ def __init__( self.children: List[Tree] = [] self.expanded = expanded self.highlight = highlight + self.hide_root = hide_root def add( self, @@ -105,6 +107,8 @@ def make_guide(index: int, style: Style) -> Segment: style_stack = StyleStack(get_style(self.style)) remove_guide_styles = Style(bold=False, underline2=False) + depth = 0 + while stack: stack_node = pop() try: @@ -123,7 +127,7 @@ def make_guide(index: int, style: Style) -> Segment: guide_style = guide_style_stack.current + get_style(node.guide_style) style = style_stack.current + get_style(node.style) - prefix = levels[1:] + prefix = levels[(2 if self.hide_root else 1) :] renderable_lines = console.render_lines( Styled(node.label, style), options.update( @@ -133,19 +137,21 @@ def make_guide(index: int, style: Style) -> Segment: height=None, ), ) - for first, line in loop_first(renderable_lines): - if prefix: - yield from _Segment.apply_style( - prefix, - style.background_style, - post_style=remove_guide_styles, - ) - yield from line - yield new_line - if first and prefix: - prefix[-1] = make_guide( - SPACE if last else CONTINUE, prefix[-1].style or null_style - ) + + if not (depth == 0 and self.hide_root): + for first, line in loop_first(renderable_lines): + if prefix: + yield from _Segment.apply_style( + prefix, + style.background_style, + post_style=remove_guide_styles, + ) + yield from line + yield new_line + if first and prefix: + prefix[-1] = make_guide( + SPACE if last else CONTINUE, prefix[-1].style or null_style + ) if node.expanded and node.children: levels[-1] = make_guide( @@ -157,6 +163,7 @@ def make_guide(index: int, style: Style) -> Segment: style_stack.push(get_style(node.style)) guide_style_stack.push(get_style(node.guide_style)) push(iter(loop_last(node.children))) + depth += 1 def __rich_measure__( self, console: "Console", options: "ConsoleOptions" @@ -222,7 +229,7 @@ class Segment(NamedTuple): """ ) - root = Tree("🌲 [b green]Rich Tree", highlight=True) + root = Tree("🌲 [b green]Rich Tree", highlight=True, hide_root=True) node = root.add(":file_folder: Renderables", guide_style="red") simple_node = node.add(":file_folder: [bold yellow]Atomic", guide_style="uu green") diff --git a/tests/_card_render.py b/tests/_card_render.py index 232191da0f..ac8d7d42df 100644 --- a/tests/_card_render.py +++ b/tests/_card_render.py @@ -1 +1 @@ -expected = """\x1b[3m Rich features \x1b[0m\n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Colors \x1b[0m\x1b[1;31m \x1b[0m✓ \x1b[1;32m4-bit color\x1b[0m \x1b[38;2;86;0;0;48;2;51;0;0m▄\x1b[0m\x1b[38;2;86;9;0;48;2;51;5;0m▄\x1b[0m\x1b[38;2;86;18;0;48;2;51;11;0m▄\x1b[0m\x1b[38;2;86;28;0;48;2;51;16;0m▄\x1b[0m\x1b[38;2;86;37;0;48;2;51;22;0m▄\x1b[0m\x1b[38;2;86;47;0;48;2;51;27;0m▄\x1b[0m\x1b[38;2;86;56;0;48;2;51;33;0m▄\x1b[0m\x1b[38;2;86;66;0;48;2;51;38;0m▄\x1b[0m\x1b[38;2;86;75;0;48;2;51;44;0m▄\x1b[0m\x1b[38;2;86;85;0;48;2;51;50;0m▄\x1b[0m\x1b[38;2;78;86;0;48;2;46;51;0m▄\x1b[0m\x1b[38;2;69;86;0;48;2;40;51;0m▄\x1b[0m\x1b[38;2;59;86;0;48;2;35;51;0m▄\x1b[0m\x1b[38;2;50;86;0;48;2;29;51;0m▄\x1b[0m\x1b[38;2;40;86;0;48;2;24;51;0m▄\x1b[0m\x1b[38;2;31;86;0;48;2;18;51;0m▄\x1b[0m\x1b[38;2;22;86;0;48;2;12;51;0m▄\x1b[0m\x1b[38;2;12;86;0;48;2;7;51;0m▄\x1b[0m\x1b[38;2;3;86;0;48;2;1;51;0m▄\x1b[0m\x1b[38;2;0;86;6;48;2;0;51;3m▄\x1b[0m\x1b[38;2;0;86;15;48;2;0;51;9m▄\x1b[0m\x1b[38;2;0;86;25;48;2;0;51;14m▄\x1b[0m\x1b[38;2;0;86;34;48;2;0;51;20m▄\x1b[0m\x1b[38;2;0;86;44;48;2;0;51;25m▄\x1b[0m\x1b[38;2;0;86;53;48;2;0;51;31m▄\x1b[0m\x1b[38;2;0;86;63;48;2;0;51;37m▄\x1b[0m\x1b[38;2;0;86;72;48;2;0;51;42m▄\x1b[0m\x1b[38;2;0;86;81;48;2;0;51;48m▄\x1b[0m\x1b[38;2;0;81;86;48;2;0;48;51m▄\x1b[0m\x1b[38;2;0;72;86;48;2;0;42;51m▄\x1b[0m\x1b[38;2;0;63;86;48;2;0;37;51m▄\x1b[0m\x1b[38;2;0;53;86;48;2;0;31;51m▄\x1b[0m\x1b[38;2;0;44;86;48;2;0;25;51m▄\x1b[0m\x1b[38;2;0;34;86;48;2;0;20;51m▄\x1b[0m\x1b[38;2;0;25;86;48;2;0;14;51m▄\x1b[0m\x1b[38;2;0;15;86;48;2;0;9;51m▄\x1b[0m\x1b[38;2;0;6;86;48;2;0;3;51m▄\x1b[0m\x1b[38;2;3;0;86;48;2;1;0;51m▄\x1b[0m\x1b[38;2;12;0;86;48;2;7;0;51m▄\x1b[0m\x1b[38;2;22;0;86;48;2;12;0;51m▄\x1b[0m\x1b[38;2;31;0;86;48;2;18;0;51m▄\x1b[0m\x1b[38;2;40;0;86;48;2;24;0;51m▄\x1b[0m\x1b[38;2;50;0;86;48;2;29;0;51m▄\x1b[0m\x1b[38;2;59;0;86;48;2;35;0;51m▄\x1b[0m\x1b[38;2;69;0;86;48;2;40;0;51m▄\x1b[0m\x1b[38;2;78;0;86;48;2;46;0;51m▄\x1b[0m\x1b[38;2;86;0;85;48;2;51;0;50m▄\x1b[0m\x1b[38;2;86;0;75;48;2;51;0;44m▄\x1b[0m\x1b[38;2;86;0;66;48;2;51;0;38m▄\x1b[0m\x1b[38;2;86;0;56;48;2;51;0;33m▄\x1b[0m\x1b[38;2;86;0;47;48;2;51;0;27m▄\x1b[0m\x1b[38;2;86;0;37;48;2;51;0;22m▄\x1b[0m\x1b[38;2;86;0;28;48;2;51;0;16m▄\x1b[0m\x1b[38;2;86;0;18;48;2;51;0;11m▄\x1b[0m\x1b[38;2;86;0;9;48;2;51;0;5m▄\x1b[0m \n ✓ \x1b[1;34m8-bit color\x1b[0m \x1b[38;2;158;0;0;48;2;122;0;0m▄\x1b[0m\x1b[38;2;158;17;0;48;2;122;13;0m▄\x1b[0m\x1b[38;2;158;34;0;48;2;122;26;0m▄\x1b[0m\x1b[38;2;158;51;0;48;2;122;40;0m▄\x1b[0m\x1b[38;2;158;68;0;48;2;122;53;0m▄\x1b[0m\x1b[38;2;158;86;0;48;2;122;66;0m▄\x1b[0m\x1b[38;2;158;103;0;48;2;122;80;0m▄\x1b[0m\x1b[38;2;158;120;0;48;2;122;93;0m▄\x1b[0m\x1b[38;2;158;137;0;48;2;122;106;0m▄\x1b[0m\x1b[38;2;158;155;0;48;2;122;120;0m▄\x1b[0m\x1b[38;2;143;158;0;48;2;111;122;0m▄\x1b[0m\x1b[38;2;126;158;0;48;2;97;122;0m▄\x1b[0m\x1b[38;2;109;158;0;48;2;84;122;0m▄\x1b[0m\x1b[38;2;91;158;0;48;2;71;122;0m▄\x1b[0m\x1b[38;2;74;158;0;48;2;57;122;0m▄\x1b[0m\x1b[38;2;57;158;0;48;2;44;122;0m▄\x1b[0m\x1b[38;2;40;158;0;48;2;31;122;0m▄\x1b[0m\x1b[38;2;22;158;0;48;2;17;122;0m▄\x1b[0m\x1b[38;2;5;158;0;48;2;4;122;0m▄\x1b[0m\x1b[38;2;0;158;11;48;2;0;122;8m▄\x1b[0m\x1b[38;2;0;158;28;48;2;0;122;22m▄\x1b[0m\x1b[38;2;0;158;45;48;2;0;122;35m▄\x1b[0m\x1b[38;2;0;158;63;48;2;0;122;48m▄\x1b[0m\x1b[38;2;0;158;80;48;2;0;122;62m▄\x1b[0m\x1b[38;2;0;158;97;48;2;0;122;75m▄\x1b[0m\x1b[38;2;0;158;114;48;2;0;122;89m▄\x1b[0m\x1b[38;2;0;158;132;48;2;0;122;102m▄\x1b[0m\x1b[38;2;0;158;149;48;2;0;122;115m▄\x1b[0m\x1b[38;2;0;149;158;48;2;0;115;122m▄\x1b[0m\x1b[38;2;0;132;158;48;2;0;102;122m▄\x1b[0m\x1b[38;2;0;114;158;48;2;0;89;122m▄\x1b[0m\x1b[38;2;0;97;158;48;2;0;75;122m▄\x1b[0m\x1b[38;2;0;80;158;48;2;0;62;122m▄\x1b[0m\x1b[38;2;0;63;158;48;2;0;48;122m▄\x1b[0m\x1b[38;2;0;45;158;48;2;0;35;122m▄\x1b[0m\x1b[38;2;0;28;158;48;2;0;22;122m▄\x1b[0m\x1b[38;2;0;11;158;48;2;0;8;122m▄\x1b[0m\x1b[38;2;5;0;158;48;2;4;0;122m▄\x1b[0m\x1b[38;2;22;0;158;48;2;17;0;122m▄\x1b[0m\x1b[38;2;40;0;158;48;2;31;0;122m▄\x1b[0m\x1b[38;2;57;0;158;48;2;44;0;122m▄\x1b[0m\x1b[38;2;74;0;158;48;2;57;0;122m▄\x1b[0m\x1b[38;2;91;0;158;48;2;71;0;122m▄\x1b[0m\x1b[38;2;109;0;158;48;2;84;0;122m▄\x1b[0m\x1b[38;2;126;0;158;48;2;97;0;122m▄\x1b[0m\x1b[38;2;143;0;158;48;2;111;0;122m▄\x1b[0m\x1b[38;2;158;0;155;48;2;122;0;120m▄\x1b[0m\x1b[38;2;158;0;137;48;2;122;0;106m▄\x1b[0m\x1b[38;2;158;0;120;48;2;122;0;93m▄\x1b[0m\x1b[38;2;158;0;103;48;2;122;0;80m▄\x1b[0m\x1b[38;2;158;0;86;48;2;122;0;66m▄\x1b[0m\x1b[38;2;158;0;68;48;2;122;0;53m▄\x1b[0m\x1b[38;2;158;0;51;48;2;122;0;40m▄\x1b[0m\x1b[38;2;158;0;34;48;2;122;0;26m▄\x1b[0m\x1b[38;2;158;0;17;48;2;122;0;13m▄\x1b[0m \n ✓ \x1b[1;35mTruecolor (16.7 million)\x1b[0m \x1b[38;2;229;0;0;48;2;193;0;0m▄\x1b[0m\x1b[38;2;229;25;0;48;2;193;21;0m▄\x1b[0m\x1b[38;2;229;50;0;48;2;193;42;0m▄\x1b[0m\x1b[38;2;229;75;0;48;2;193;63;0m▄\x1b[0m\x1b[38;2;229;100;0;48;2;193;84;0m▄\x1b[0m\x1b[38;2;229;125;0;48;2;193;105;0m▄\x1b[0m\x1b[38;2;229;150;0;48;2;193;126;0m▄\x1b[0m\x1b[38;2;229;175;0;48;2;193;147;0m▄\x1b[0m\x1b[38;2;229;200;0;48;2;193;169;0m▄\x1b[0m\x1b[38;2;229;225;0;48;2;193;190;0m▄\x1b[0m\x1b[38;2;208;229;0;48;2;176;193;0m▄\x1b[0m\x1b[38;2;183;229;0;48;2;155;193;0m▄\x1b[0m\x1b[38;2;158;229;0;48;2;133;193;0m▄\x1b[0m\x1b[38;2;133;229;0;48;2;112;193;0m▄\x1b[0m\x1b[38;2;108;229;0;48;2;91;193;0m▄\x1b[0m\x1b[38;2;83;229;0;48;2;70;193;0m▄\x1b[0m\x1b[38;2;58;229;0;48;2;49;193;0m▄\x1b[0m\x1b[38;2;33;229;0;48;2;28;193;0m▄\x1b[0m\x1b[38;2;8;229;0;48;2;7;193;0m▄\x1b[0m\x1b[38;2;0;229;16;48;2;0;193;14m▄\x1b[0m\x1b[38;2;0;229;41;48;2;0;193;35m▄\x1b[0m\x1b[38;2;0;229;66;48;2;0;193;56m▄\x1b[0m\x1b[38;2;0;229;91;48;2;0;193;77m▄\x1b[0m\x1b[38;2;0;229;116;48;2;0;193;98m▄\x1b[0m\x1b[38;2;0;229;141;48;2;0;193;119m▄\x1b[0m\x1b[38;2;0;229;166;48;2;0;193;140m▄\x1b[0m\x1b[38;2;0;229;191;48;2;0;193;162m▄\x1b[0m\x1b[38;2;0;229;216;48;2;0;193;183m▄\x1b[0m\x1b[38;2;0;216;229;48;2;0;183;193m▄\x1b[0m\x1b[38;2;0;191;229;48;2;0;162;193m▄\x1b[0m\x1b[38;2;0;166;229;48;2;0;140;193m▄\x1b[0m\x1b[38;2;0;141;229;48;2;0;119;193m▄\x1b[0m\x1b[38;2;0;116;229;48;2;0;98;193m▄\x1b[0m\x1b[38;2;0;91;229;48;2;0;77;193m▄\x1b[0m\x1b[38;2;0;66;229;48;2;0;56;193m▄\x1b[0m\x1b[38;2;0;41;229;48;2;0;35;193m▄\x1b[0m\x1b[38;2;0;16;229;48;2;0;14;193m▄\x1b[0m\x1b[38;2;8;0;229;48;2;7;0;193m▄\x1b[0m\x1b[38;2;33;0;229;48;2;28;0;193m▄\x1b[0m\x1b[38;2;58;0;229;48;2;49;0;193m▄\x1b[0m\x1b[38;2;83;0;229;48;2;70;0;193m▄\x1b[0m\x1b[38;2;108;0;229;48;2;91;0;193m▄\x1b[0m\x1b[38;2;133;0;229;48;2;112;0;193m▄\x1b[0m\x1b[38;2;158;0;229;48;2;133;0;193m▄\x1b[0m\x1b[38;2;183;0;229;48;2;155;0;193m▄\x1b[0m\x1b[38;2;208;0;229;48;2;176;0;193m▄\x1b[0m\x1b[38;2;229;0;225;48;2;193;0;190m▄\x1b[0m\x1b[38;2;229;0;200;48;2;193;0;169m▄\x1b[0m\x1b[38;2;229;0;175;48;2;193;0;147m▄\x1b[0m\x1b[38;2;229;0;150;48;2;193;0;126m▄\x1b[0m\x1b[38;2;229;0;125;48;2;193;0;105m▄\x1b[0m\x1b[38;2;229;0;100;48;2;193;0;84m▄\x1b[0m\x1b[38;2;229;0;75;48;2;193;0;63m▄\x1b[0m\x1b[38;2;229;0;50;48;2;193;0;42m▄\x1b[0m\x1b[38;2;229;0;25;48;2;193;0;21m▄\x1b[0m \n ✓ \x1b[1;33mDumb terminals\x1b[0m \x1b[38;2;254;45;45;48;2;255;10;10m▄\x1b[0m\x1b[38;2;254;68;45;48;2;255;36;10m▄\x1b[0m\x1b[38;2;254;91;45;48;2;255;63;10m▄\x1b[0m\x1b[38;2;254;114;45;48;2;255;90;10m▄\x1b[0m\x1b[38;2;254;137;45;48;2;255;117;10m▄\x1b[0m\x1b[38;2;254;159;45;48;2;255;143;10m▄\x1b[0m\x1b[38;2;254;182;45;48;2;255;170;10m▄\x1b[0m\x1b[38;2;254;205;45;48;2;255;197;10m▄\x1b[0m\x1b[38;2;254;228;45;48;2;255;223;10m▄\x1b[0m\x1b[38;2;254;251;45;48;2;255;250;10m▄\x1b[0m\x1b[38;2;235;254;45;48;2;232;255;10m▄\x1b[0m\x1b[38;2;213;254;45;48;2;206;255;10m▄\x1b[0m\x1b[38;2;190;254;45;48;2;179;255;10m▄\x1b[0m\x1b[38;2;167;254;45;48;2;152;255;10m▄\x1b[0m\x1b[38;2;144;254;45;48;2;125;255;10m▄\x1b[0m\x1b[38;2;121;254;45;48;2;99;255;10m▄\x1b[0m\x1b[38;2;99;254;45;48;2;72;255;10m▄\x1b[0m\x1b[38;2;76;254;45;48;2;45;255;10m▄\x1b[0m\x1b[38;2;53;254;45;48;2;19;255;10m▄\x1b[0m\x1b[38;2;45;254;61;48;2;10;255;28m▄\x1b[0m\x1b[38;2;45;254;83;48;2;10;255;54m▄\x1b[0m\x1b[38;2;45;254;106;48;2;10;255;81m▄\x1b[0m\x1b[38;2;45;254;129;48;2;10;255;108m▄\x1b[0m\x1b[38;2;45;254;152;48;2;10;255;134m▄\x1b[0m\x1b[38;2;45;254;175;48;2;10;255;161m▄\x1b[0m\x1b[38;2;45;254;197;48;2;10;255;188m▄\x1b[0m\x1b[38;2;45;254;220;48;2;10;255;214m▄\x1b[0m\x1b[38;2;45;254;243;48;2;10;255;241m▄\x1b[0m\x1b[38;2;45;243;254;48;2;10;241;255m▄\x1b[0m\x1b[38;2;45;220;254;48;2;10;214;255m▄\x1b[0m\x1b[38;2;45;197;254;48;2;10;188;255m▄\x1b[0m\x1b[38;2;45;175;254;48;2;10;161;255m▄\x1b[0m\x1b[38;2;45;152;254;48;2;10;134;255m▄\x1b[0m\x1b[38;2;45;129;254;48;2;10;108;255m▄\x1b[0m\x1b[38;2;45;106;254;48;2;10;81;255m▄\x1b[0m\x1b[38;2;45;83;254;48;2;10;54;255m▄\x1b[0m\x1b[38;2;45;61;254;48;2;10;28;255m▄\x1b[0m\x1b[38;2;53;45;254;48;2;19;10;255m▄\x1b[0m\x1b[38;2;76;45;254;48;2;45;10;255m▄\x1b[0m\x1b[38;2;99;45;254;48;2;72;10;255m▄\x1b[0m\x1b[38;2;121;45;254;48;2;99;10;255m▄\x1b[0m\x1b[38;2;144;45;254;48;2;125;10;255m▄\x1b[0m\x1b[38;2;167;45;254;48;2;152;10;255m▄\x1b[0m\x1b[38;2;190;45;254;48;2;179;10;255m▄\x1b[0m\x1b[38;2;213;45;254;48;2;206;10;255m▄\x1b[0m\x1b[38;2;235;45;254;48;2;232;10;255m▄\x1b[0m\x1b[38;2;254;45;251;48;2;255;10;250m▄\x1b[0m\x1b[38;2;254;45;228;48;2;255;10;223m▄\x1b[0m\x1b[38;2;254;45;205;48;2;255;10;197m▄\x1b[0m\x1b[38;2;254;45;182;48;2;255;10;170m▄\x1b[0m\x1b[38;2;254;45;159;48;2;255;10;143m▄\x1b[0m\x1b[38;2;254;45;137;48;2;255;10;117m▄\x1b[0m\x1b[38;2;254;45;114;48;2;255;10;90m▄\x1b[0m\x1b[38;2;254;45;91;48;2;255;10;63m▄\x1b[0m\x1b[38;2;254;45;68;48;2;255;10;36m▄\x1b[0m \n ✓ \x1b[1;36mAutomatic color conversion\x1b[0m \x1b[38;2;255;117;117;48;2;255;81;81m▄\x1b[0m\x1b[38;2;255;132;117;48;2;255;100;81m▄\x1b[0m\x1b[38;2;255;147;117;48;2;255;119;81m▄\x1b[0m\x1b[38;2;255;162;117;48;2;255;138;81m▄\x1b[0m\x1b[38;2;255;177;117;48;2;255;157;81m▄\x1b[0m\x1b[38;2;255;192;117;48;2;255;176;81m▄\x1b[0m\x1b[38;2;255;207;117;48;2;255;195;81m▄\x1b[0m\x1b[38;2;255;222;117;48;2;255;214;81m▄\x1b[0m\x1b[38;2;255;237;117;48;2;255;232;81m▄\x1b[0m\x1b[38;2;255;252;117;48;2;255;251;81m▄\x1b[0m\x1b[38;2;242;255;117;48;2;239;255;81m▄\x1b[0m\x1b[38;2;227;255;117;48;2;220;255;81m▄\x1b[0m\x1b[38;2;212;255;117;48;2;201;255;81m▄\x1b[0m\x1b[38;2;197;255;117;48;2;182;255;81m▄\x1b[0m\x1b[38;2;182;255;117;48;2;163;255;81m▄\x1b[0m\x1b[38;2;167;255;117;48;2;144;255;81m▄\x1b[0m\x1b[38;2;152;255;117;48;2;125;255;81m▄\x1b[0m\x1b[38;2;137;255;117;48;2;106;255;81m▄\x1b[0m\x1b[38;2;122;255;117;48;2;87;255;81m▄\x1b[0m\x1b[38;2;117;255;127;48;2;81;255;94m▄\x1b[0m\x1b[38;2;117;255;142;48;2;81;255;113m▄\x1b[0m\x1b[38;2;117;255;157;48;2;81;255;132m▄\x1b[0m\x1b[38;2;117;255;172;48;2;81;255;150m▄\x1b[0m\x1b[38;2;117;255;187;48;2;81;255;169m▄\x1b[0m\x1b[38;2;117;255;202;48;2;81;255;188m▄\x1b[0m\x1b[38;2;117;255;217;48;2;81;255;207m▄\x1b[0m\x1b[38;2;117;255;232;48;2;81;255;226m▄\x1b[0m\x1b[38;2;117;255;247;48;2;81;255;245m▄\x1b[0m\x1b[38;2;117;247;255;48;2;81;245;255m▄\x1b[0m\x1b[38;2;117;232;255;48;2;81;226;255m▄\x1b[0m\x1b[38;2;117;217;255;48;2;81;207;255m▄\x1b[0m\x1b[38;2;117;202;255;48;2;81;188;255m▄\x1b[0m\x1b[38;2;117;187;255;48;2;81;169;255m▄\x1b[0m\x1b[38;2;117;172;255;48;2;81;150;255m▄\x1b[0m\x1b[38;2;117;157;255;48;2;81;132;255m▄\x1b[0m\x1b[38;2;117;142;255;48;2;81;113;255m▄\x1b[0m\x1b[38;2;117;127;255;48;2;81;94;255m▄\x1b[0m\x1b[38;2;122;117;255;48;2;87;81;255m▄\x1b[0m\x1b[38;2;137;117;255;48;2;106;81;255m▄\x1b[0m\x1b[38;2;152;117;255;48;2;125;81;255m▄\x1b[0m\x1b[38;2;167;117;255;48;2;144;81;255m▄\x1b[0m\x1b[38;2;182;117;255;48;2;163;81;255m▄\x1b[0m\x1b[38;2;197;117;255;48;2;182;81;255m▄\x1b[0m\x1b[38;2;212;117;255;48;2;201;81;255m▄\x1b[0m\x1b[38;2;227;117;255;48;2;220;81;255m▄\x1b[0m\x1b[38;2;242;117;255;48;2;239;81;255m▄\x1b[0m\x1b[38;2;255;117;252;48;2;255;81;251m▄\x1b[0m\x1b[38;2;255;117;237;48;2;255;81;232m▄\x1b[0m\x1b[38;2;255;117;222;48;2;255;81;214m▄\x1b[0m\x1b[38;2;255;117;207;48;2;255;81;195m▄\x1b[0m\x1b[38;2;255;117;192;48;2;255;81;176m▄\x1b[0m\x1b[38;2;255;117;177;48;2;255;81;157m▄\x1b[0m\x1b[38;2;255;117;162;48;2;255;81;138m▄\x1b[0m\x1b[38;2;255;117;147;48;2;255;81;119m▄\x1b[0m\x1b[38;2;255;117;132;48;2;255;81;100m▄\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Styles \x1b[0m\x1b[1;31m \x1b[0mAll ansi styles: \x1b[1mbold\x1b[0m, \x1b[2mdim\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[4munderline\x1b[0m, \x1b[9mstrikethrough\x1b[0m, \x1b[7mreverse\x1b[0m, and even \n \x1b[5mblink\x1b[0m. \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Text \x1b[0m\x1b[1;31m \x1b[0mWord wrap text. Justify \x1b[32mleft\x1b[0m, \x1b[33mcenter\x1b[0m, \x1b[34mright\x1b[0m or \x1b[31mfull\x1b[0m. \n \n \x1b[32mLorem ipsum dolor \x1b[0m \x1b[33m Lorem ipsum dolor \x1b[0m \x1b[34m Lorem ipsum dolor\x1b[0m \x1b[31mLorem\x1b[0m\x1b[31m \x1b[0m\x1b[31mipsum\x1b[0m\x1b[31m \x1b[0m\x1b[31mdolor\x1b[0m\x1b[31m \x1b[0m\x1b[31msit\x1b[0m \n \x1b[32msit amet, \x1b[0m \x1b[33m sit amet, \x1b[0m \x1b[34m sit amet,\x1b[0m \x1b[31mamet,\x1b[0m\x1b[31m \x1b[0m\x1b[31mconsectetur\x1b[0m \n \x1b[32mconsectetur \x1b[0m \x1b[33m consectetur \x1b[0m \x1b[34m consectetur\x1b[0m \x1b[31madipiscing\x1b[0m\x1b[31m \x1b[0m\x1b[31melit.\x1b[0m \n \x1b[32madipiscing elit. \x1b[0m \x1b[33m adipiscing elit. \x1b[0m \x1b[34m adipiscing elit.\x1b[0m \x1b[31mQuisque\x1b[0m\x1b[31m \x1b[0m\x1b[31min\x1b[0m\x1b[31m \x1b[0m\x1b[31mmetus\x1b[0m\x1b[31m \x1b[0m\x1b[31msed\x1b[0m \n \x1b[32mQuisque in metus sed\x1b[0m \x1b[33mQuisque in metus sed\x1b[0m \x1b[34mQuisque in metus sed\x1b[0m \x1b[31msapien\x1b[0m\x1b[31m \x1b[0m\x1b[31multricies\x1b[0m \n \x1b[32msapien ultricies \x1b[0m \x1b[33m sapien ultricies \x1b[0m \x1b[34m sapien ultricies\x1b[0m \x1b[31mpretium\x1b[0m\x1b[31m \x1b[0m\x1b[31ma\x1b[0m\x1b[31m \x1b[0m\x1b[31mat\x1b[0m\x1b[31m \x1b[0m\x1b[31mjusto.\x1b[0m \n \x1b[32mpretium a at justo. \x1b[0m \x1b[33mpretium a at justo. \x1b[0m \x1b[34m pretium a at justo.\x1b[0m \x1b[31mMaecenas\x1b[0m\x1b[31m \x1b[0m\x1b[31mluctus\x1b[0m\x1b[31m \x1b[0m\x1b[31mvelit\x1b[0m \n \x1b[32mMaecenas luctus \x1b[0m \x1b[33m Maecenas luctus \x1b[0m \x1b[34m Maecenas luctus\x1b[0m \x1b[31met auctor maximus.\x1b[0m \n \x1b[32mvelit et auctor \x1b[0m \x1b[33m velit et auctor \x1b[0m \x1b[34m velit et auctor\x1b[0m \n \x1b[32mmaximus. \x1b[0m \x1b[33m maximus. \x1b[0m \x1b[34m maximus.\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Asian \x1b[0m\x1b[1;31m \x1b[0m🇨🇳 该库支持中文,日文和韩文文本! \n\x1b[1;31m \x1b[0m\x1b[1;31m language \x1b[0m\x1b[1;31m \x1b[0m🇯🇵 ライブラリは中国語、日本語、韓国語のテキストをサポートしています \n\x1b[1;31m \x1b[0m\x1b[1;31m support \x1b[0m\x1b[1;31m \x1b[0m🇰🇷 이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다 \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Markup \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;35mRich\x1b[0m supports a simple \x1b[3mbbcode\x1b[0m-like \x1b[1mmarkup\x1b[0m for \x1b[33mcolor\x1b[0m, \x1b[4mstyle\x1b[0m, and emoji! 👍 🍎 🐜 🐻 … \n 🚌 \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Tables \x1b[0m\x1b[1;31m \x1b[0m\x1b[1m \x1b[0m\x1b[1;32mDate\x1b[0m\x1b[1m \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1;34mTitle\x1b[0m\x1b[1m \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1;36mProduction Budget\x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1m \x1b[0m\x1b[1;35mBox Office\x1b[0m\x1b[1m \x1b[0m \n ───────────────────────────────────────────────────────────────────────────────────── \n \x1b[32m \x1b[0m\x1b[32mDec 20, 2019\x1b[0m\x1b[32m \x1b[0m \x1b[34m \x1b[0m\x1b[34mStar Wars: The Rise of \x1b[0m\x1b[34m \x1b[0m \x1b[36m \x1b[0m\x1b[36m $275,000,000\x1b[0m\x1b[36m \x1b[0m \x1b[35m \x1b[0m\x1b[35m $375,126,118\x1b[0m\x1b[35m \x1b[0m \n \x1b[34m \x1b[0m\x1b[34mSkywalker \x1b[0m\x1b[34m \x1b[0m \n \x1b[2;32m \x1b[0m\x1b[2;32mMay 25, 2018\x1b[0m\x1b[2;32m \x1b[0m \x1b[2;34m \x1b[0m\x1b[1;2;34mSolo\x1b[0m\x1b[2;34m: A Star Wars Story \x1b[0m\x1b[2;34m \x1b[0m \x1b[2;36m \x1b[0m\x1b[2;36m $275,000,000\x1b[0m\x1b[2;36m \x1b[0m \x1b[2;35m \x1b[0m\x1b[2;35m $393,151,347\x1b[0m\x1b[2;35m \x1b[0m \n \x1b[32m \x1b[0m\x1b[32mDec 15, 2017\x1b[0m\x1b[32m \x1b[0m \x1b[34m \x1b[0m\x1b[34mStar Wars Ep. VIII: The Last \x1b[0m\x1b[34m \x1b[0m \x1b[36m \x1b[0m\x1b[36m $262,000,000\x1b[0m\x1b[36m \x1b[0m \x1b[35m \x1b[0m\x1b[1;35m$1,332,539,889\x1b[0m\x1b[35m \x1b[0m \n \x1b[34m \x1b[0m\x1b[34mJedi \x1b[0m\x1b[34m \x1b[0m \n \x1b[2;32m \x1b[0m\x1b[2;32mMay 19, 1999\x1b[0m\x1b[2;32m \x1b[0m \x1b[2;34m \x1b[0m\x1b[2;34mStar Wars Ep. \x1b[0m\x1b[1;2;34mI\x1b[0m\x1b[2;34m: \x1b[0m\x1b[2;3;34mThe phantom \x1b[0m\x1b[2;34m \x1b[0m\x1b[2;34m \x1b[0m \x1b[2;36m \x1b[0m\x1b[2;36m $115,000,000\x1b[0m\x1b[2;36m \x1b[0m \x1b[2;35m \x1b[0m\x1b[2;35m$1,027,044,677\x1b[0m\x1b[2;35m \x1b[0m \n \x1b[2m \x1b[0m \x1b[2;34m \x1b[0m\x1b[2;3;34mMenace\x1b[0m\x1b[2;34m \x1b[0m\x1b[2;34m \x1b[0m \x1b[2m \x1b[0m \x1b[2m \x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Syntax \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 1 \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mdef\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34miter_last\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mIterable\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m[\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mT\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m]\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m-\x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m>\x1b[0m \x1b[1m{\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31mhighlighting\x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 2 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;230;219;116;48;2;39;40;34m\"""Iterate and generate a tuple w\x1b[0m \x1b[2;32m│ \x1b[0m\x1b[32m\'foo\'\x1b[0m: \x1b[1m[\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m & \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 3 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1;36m3.1427\x1b[0m, \n\x1b[1;31m \x1b[0m\x1b[1;31m pretty \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 4 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mtry\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1m(\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m printing \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 5 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mnext\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_va\x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m\'Paul Atreides\'\x1b[0m, \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 6 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mexcept\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34mStopIteration\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m\'Vladimir Harkonnen\'\x1b[0m, \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 7 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mreturn\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m\'Thufir Hawat\'\x1b[0m \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 8 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mfor\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34min\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1m)\x1b[0m \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 9 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mFalse\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ \x1b[0m\x1b[1m]\x1b[0m, \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m10 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ \x1b[0m\x1b[32m\'atomic\'\x1b[0m: \x1b[1m(\x1b[0m\x1b[3;91mFalse\x1b[0m, \x1b[3;92mTrue\x1b[0m, \x1b[3;35mNone\x1b[0m\x1b[1m)\x1b[0m \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m11 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mTrue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[1m}\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Markdown \x1b[0m\x1b[1;31m \x1b[0m\x1b[36m# Markdown\x1b[0m ╔═══════════════════════════════════════╗ \n ║ \x1b[1mMarkdown\x1b[0m ║ \n \x1b[36mSupports much of the *markdown* \x1b[0m ╚═══════════════════════════════════════╝ \n \x1b[36m__syntax__!\x1b[0m \n Supports much of the \x1b[3mmarkdown\x1b[0m \x1b[1msyntax\x1b[0m! \n \x1b[36m- Headers\x1b[0m \n \x1b[36m- Basic formatting: **bold**, *italic*, \x1b[0m \x1b[1;33m • \x1b[0mHeaders \n \x1b[36m`code`\x1b[0m \x1b[1;33m • \x1b[0mBasic formatting: \x1b[1mbold\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[97;40mcode\x1b[0m \n \x1b[36m- Block quotes\x1b[0m \x1b[1;33m • \x1b[0mBlock quotes \n \x1b[36m- Lists, and more...\x1b[0m \x1b[1;33m • \x1b[0mLists, and more... \n \x1b[36m \x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m +more! \x1b[0m\x1b[1;31m \x1b[0mProgress bars, columns, styled logging handler, tracebacks, etc... \n\x1b[1;31m \x1b[0m \n""" +expected = "\x1b[3m Rich features \x1b[0m\n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Colors \x1b[0m\x1b[1;31m \x1b[0m✓ \x1b[1;32m4-bit color\x1b[0m \x1b[38;2;86;0;0;48;2;51;0;0m▄\x1b[0m\x1b[38;2;86;9;0;48;2;51;5;0m▄\x1b[0m\x1b[38;2;86;18;0;48;2;51;11;0m▄\x1b[0m\x1b[38;2;86;28;0;48;2;51;16;0m▄\x1b[0m\x1b[38;2;86;37;0;48;2;51;22;0m▄\x1b[0m\x1b[38;2;86;47;0;48;2;51;27;0m▄\x1b[0m\x1b[38;2;86;56;0;48;2;51;33;0m▄\x1b[0m\x1b[38;2;86;66;0;48;2;51;38;0m▄\x1b[0m\x1b[38;2;86;75;0;48;2;51;44;0m▄\x1b[0m\x1b[38;2;86;85;0;48;2;51;50;0m▄\x1b[0m\x1b[38;2;78;86;0;48;2;46;51;0m▄\x1b[0m\x1b[38;2;69;86;0;48;2;40;51;0m▄\x1b[0m\x1b[38;2;59;86;0;48;2;35;51;0m▄\x1b[0m\x1b[38;2;50;86;0;48;2;29;51;0m▄\x1b[0m\x1b[38;2;40;86;0;48;2;24;51;0m▄\x1b[0m\x1b[38;2;31;86;0;48;2;18;51;0m▄\x1b[0m\x1b[38;2;22;86;0;48;2;12;51;0m▄\x1b[0m\x1b[38;2;12;86;0;48;2;7;51;0m▄\x1b[0m\x1b[38;2;3;86;0;48;2;1;51;0m▄\x1b[0m\x1b[38;2;0;86;6;48;2;0;51;3m▄\x1b[0m\x1b[38;2;0;86;15;48;2;0;51;9m▄\x1b[0m\x1b[38;2;0;86;25;48;2;0;51;14m▄\x1b[0m\x1b[38;2;0;86;34;48;2;0;51;20m▄\x1b[0m\x1b[38;2;0;86;44;48;2;0;51;25m▄\x1b[0m\x1b[38;2;0;86;53;48;2;0;51;31m▄\x1b[0m\x1b[38;2;0;86;63;48;2;0;51;37m▄\x1b[0m\x1b[38;2;0;86;72;48;2;0;51;42m▄\x1b[0m\x1b[38;2;0;86;81;48;2;0;51;48m▄\x1b[0m\x1b[38;2;0;81;86;48;2;0;48;51m▄\x1b[0m\x1b[38;2;0;72;86;48;2;0;42;51m▄\x1b[0m\x1b[38;2;0;63;86;48;2;0;37;51m▄\x1b[0m\x1b[38;2;0;53;86;48;2;0;31;51m▄\x1b[0m\x1b[38;2;0;44;86;48;2;0;25;51m▄\x1b[0m\x1b[38;2;0;34;86;48;2;0;20;51m▄\x1b[0m\x1b[38;2;0;25;86;48;2;0;14;51m▄\x1b[0m\x1b[38;2;0;15;86;48;2;0;9;51m▄\x1b[0m\x1b[38;2;0;6;86;48;2;0;3;51m▄\x1b[0m\x1b[38;2;3;0;86;48;2;1;0;51m▄\x1b[0m\x1b[38;2;12;0;86;48;2;7;0;51m▄\x1b[0m\x1b[38;2;22;0;86;48;2;12;0;51m▄\x1b[0m\x1b[38;2;31;0;86;48;2;18;0;51m▄\x1b[0m\x1b[38;2;40;0;86;48;2;24;0;51m▄\x1b[0m\x1b[38;2;50;0;86;48;2;29;0;51m▄\x1b[0m\x1b[38;2;59;0;86;48;2;35;0;51m▄\x1b[0m\x1b[38;2;69;0;86;48;2;40;0;51m▄\x1b[0m\x1b[38;2;78;0;86;48;2;46;0;51m▄\x1b[0m\x1b[38;2;86;0;85;48;2;51;0;50m▄\x1b[0m\x1b[38;2;86;0;75;48;2;51;0;44m▄\x1b[0m\x1b[38;2;86;0;66;48;2;51;0;38m▄\x1b[0m\x1b[38;2;86;0;56;48;2;51;0;33m▄\x1b[0m\x1b[38;2;86;0;47;48;2;51;0;27m▄\x1b[0m\x1b[38;2;86;0;37;48;2;51;0;22m▄\x1b[0m\x1b[38;2;86;0;28;48;2;51;0;16m▄\x1b[0m\x1b[38;2;86;0;18;48;2;51;0;11m▄\x1b[0m\x1b[38;2;86;0;9;48;2;51;0;5m▄\x1b[0m \n\x1b[1;31m \x1b[0m✓ \x1b[1;34m8-bit color\x1b[0m \x1b[38;2;158;0;0;48;2;122;0;0m▄\x1b[0m\x1b[38;2;158;17;0;48;2;122;13;0m▄\x1b[0m\x1b[38;2;158;34;0;48;2;122;26;0m▄\x1b[0m\x1b[38;2;158;51;0;48;2;122;40;0m▄\x1b[0m\x1b[38;2;158;68;0;48;2;122;53;0m▄\x1b[0m\x1b[38;2;158;86;0;48;2;122;66;0m▄\x1b[0m\x1b[38;2;158;103;0;48;2;122;80;0m▄\x1b[0m\x1b[38;2;158;120;0;48;2;122;93;0m▄\x1b[0m\x1b[38;2;158;137;0;48;2;122;106;0m▄\x1b[0m\x1b[38;2;158;155;0;48;2;122;120;0m▄\x1b[0m\x1b[38;2;143;158;0;48;2;111;122;0m▄\x1b[0m\x1b[38;2;126;158;0;48;2;97;122;0m▄\x1b[0m\x1b[38;2;109;158;0;48;2;84;122;0m▄\x1b[0m\x1b[38;2;91;158;0;48;2;71;122;0m▄\x1b[0m\x1b[38;2;74;158;0;48;2;57;122;0m▄\x1b[0m\x1b[38;2;57;158;0;48;2;44;122;0m▄\x1b[0m\x1b[38;2;40;158;0;48;2;31;122;0m▄\x1b[0m\x1b[38;2;22;158;0;48;2;17;122;0m▄\x1b[0m\x1b[38;2;5;158;0;48;2;4;122;0m▄\x1b[0m\x1b[38;2;0;158;11;48;2;0;122;8m▄\x1b[0m\x1b[38;2;0;158;28;48;2;0;122;22m▄\x1b[0m\x1b[38;2;0;158;45;48;2;0;122;35m▄\x1b[0m\x1b[38;2;0;158;63;48;2;0;122;48m▄\x1b[0m\x1b[38;2;0;158;80;48;2;0;122;62m▄\x1b[0m\x1b[38;2;0;158;97;48;2;0;122;75m▄\x1b[0m\x1b[38;2;0;158;114;48;2;0;122;89m▄\x1b[0m\x1b[38;2;0;158;132;48;2;0;122;102m▄\x1b[0m\x1b[38;2;0;158;149;48;2;0;122;115m▄\x1b[0m\x1b[38;2;0;149;158;48;2;0;115;122m▄\x1b[0m\x1b[38;2;0;132;158;48;2;0;102;122m▄\x1b[0m\x1b[38;2;0;114;158;48;2;0;89;122m▄\x1b[0m\x1b[38;2;0;97;158;48;2;0;75;122m▄\x1b[0m\x1b[38;2;0;80;158;48;2;0;62;122m▄\x1b[0m\x1b[38;2;0;63;158;48;2;0;48;122m▄\x1b[0m\x1b[38;2;0;45;158;48;2;0;35;122m▄\x1b[0m\x1b[38;2;0;28;158;48;2;0;22;122m▄\x1b[0m\x1b[38;2;0;11;158;48;2;0;8;122m▄\x1b[0m\x1b[38;2;5;0;158;48;2;4;0;122m▄\x1b[0m\x1b[38;2;22;0;158;48;2;17;0;122m▄\x1b[0m\x1b[38;2;40;0;158;48;2;31;0;122m▄\x1b[0m\x1b[38;2;57;0;158;48;2;44;0;122m▄\x1b[0m\x1b[38;2;74;0;158;48;2;57;0;122m▄\x1b[0m\x1b[38;2;91;0;158;48;2;71;0;122m▄\x1b[0m\x1b[38;2;109;0;158;48;2;84;0;122m▄\x1b[0m\x1b[38;2;126;0;158;48;2;97;0;122m▄\x1b[0m\x1b[38;2;143;0;158;48;2;111;0;122m▄\x1b[0m\x1b[38;2;158;0;155;48;2;122;0;120m▄\x1b[0m\x1b[38;2;158;0;137;48;2;122;0;106m▄\x1b[0m\x1b[38;2;158;0;120;48;2;122;0;93m▄\x1b[0m\x1b[38;2;158;0;103;48;2;122;0;80m▄\x1b[0m\x1b[38;2;158;0;86;48;2;122;0;66m▄\x1b[0m\x1b[38;2;158;0;68;48;2;122;0;53m▄\x1b[0m\x1b[38;2;158;0;51;48;2;122;0;40m▄\x1b[0m\x1b[38;2;158;0;34;48;2;122;0;26m▄\x1b[0m\x1b[38;2;158;0;17;48;2;122;0;13m▄\x1b[0m \n\x1b[1;31m \x1b[0m✓ \x1b[1;35mTruecolor (16.7 million)\x1b[0m \x1b[38;2;229;0;0;48;2;193;0;0m▄\x1b[0m\x1b[38;2;229;25;0;48;2;193;21;0m▄\x1b[0m\x1b[38;2;229;50;0;48;2;193;42;0m▄\x1b[0m\x1b[38;2;229;75;0;48;2;193;63;0m▄\x1b[0m\x1b[38;2;229;100;0;48;2;193;84;0m▄\x1b[0m\x1b[38;2;229;125;0;48;2;193;105;0m▄\x1b[0m\x1b[38;2;229;150;0;48;2;193;126;0m▄\x1b[0m\x1b[38;2;229;175;0;48;2;193;147;0m▄\x1b[0m\x1b[38;2;229;200;0;48;2;193;169;0m▄\x1b[0m\x1b[38;2;229;225;0;48;2;193;190;0m▄\x1b[0m\x1b[38;2;208;229;0;48;2;176;193;0m▄\x1b[0m\x1b[38;2;183;229;0;48;2;155;193;0m▄\x1b[0m\x1b[38;2;158;229;0;48;2;133;193;0m▄\x1b[0m\x1b[38;2;133;229;0;48;2;112;193;0m▄\x1b[0m\x1b[38;2;108;229;0;48;2;91;193;0m▄\x1b[0m\x1b[38;2;83;229;0;48;2;70;193;0m▄\x1b[0m\x1b[38;2;58;229;0;48;2;49;193;0m▄\x1b[0m\x1b[38;2;33;229;0;48;2;28;193;0m▄\x1b[0m\x1b[38;2;8;229;0;48;2;7;193;0m▄\x1b[0m\x1b[38;2;0;229;16;48;2;0;193;14m▄\x1b[0m\x1b[38;2;0;229;41;48;2;0;193;35m▄\x1b[0m\x1b[38;2;0;229;66;48;2;0;193;56m▄\x1b[0m\x1b[38;2;0;229;91;48;2;0;193;77m▄\x1b[0m\x1b[38;2;0;229;116;48;2;0;193;98m▄\x1b[0m\x1b[38;2;0;229;141;48;2;0;193;119m▄\x1b[0m\x1b[38;2;0;229;166;48;2;0;193;140m▄\x1b[0m\x1b[38;2;0;229;191;48;2;0;193;162m▄\x1b[0m\x1b[38;2;0;229;216;48;2;0;193;183m▄\x1b[0m\x1b[38;2;0;216;229;48;2;0;183;193m▄\x1b[0m\x1b[38;2;0;191;229;48;2;0;162;193m▄\x1b[0m\x1b[38;2;0;166;229;48;2;0;140;193m▄\x1b[0m\x1b[38;2;0;141;229;48;2;0;119;193m▄\x1b[0m\x1b[38;2;0;116;229;48;2;0;98;193m▄\x1b[0m\x1b[38;2;0;91;229;48;2;0;77;193m▄\x1b[0m\x1b[38;2;0;66;229;48;2;0;56;193m▄\x1b[0m\x1b[38;2;0;41;229;48;2;0;35;193m▄\x1b[0m\x1b[38;2;0;16;229;48;2;0;14;193m▄\x1b[0m\x1b[38;2;8;0;229;48;2;7;0;193m▄\x1b[0m\x1b[38;2;33;0;229;48;2;28;0;193m▄\x1b[0m\x1b[38;2;58;0;229;48;2;49;0;193m▄\x1b[0m\x1b[38;2;83;0;229;48;2;70;0;193m▄\x1b[0m\x1b[38;2;108;0;229;48;2;91;0;193m▄\x1b[0m\x1b[38;2;133;0;229;48;2;112;0;193m▄\x1b[0m\x1b[38;2;158;0;229;48;2;133;0;193m▄\x1b[0m\x1b[38;2;183;0;229;48;2;155;0;193m▄\x1b[0m\x1b[38;2;208;0;229;48;2;176;0;193m▄\x1b[0m\x1b[38;2;229;0;225;48;2;193;0;190m▄\x1b[0m\x1b[38;2;229;0;200;48;2;193;0;169m▄\x1b[0m\x1b[38;2;229;0;175;48;2;193;0;147m▄\x1b[0m\x1b[38;2;229;0;150;48;2;193;0;126m▄\x1b[0m\x1b[38;2;229;0;125;48;2;193;0;105m▄\x1b[0m\x1b[38;2;229;0;100;48;2;193;0;84m▄\x1b[0m\x1b[38;2;229;0;75;48;2;193;0;63m▄\x1b[0m\x1b[38;2;229;0;50;48;2;193;0;42m▄\x1b[0m\x1b[38;2;229;0;25;48;2;193;0;21m▄\x1b[0m \n\x1b[1;31m \x1b[0m✓ \x1b[1;33mDumb terminals\x1b[0m \x1b[38;2;254;45;45;48;2;255;10;10m▄\x1b[0m\x1b[38;2;254;68;45;48;2;255;36;10m▄\x1b[0m\x1b[38;2;254;91;45;48;2;255;63;10m▄\x1b[0m\x1b[38;2;254;114;45;48;2;255;90;10m▄\x1b[0m\x1b[38;2;254;137;45;48;2;255;117;10m▄\x1b[0m\x1b[38;2;254;159;45;48;2;255;143;10m▄\x1b[0m\x1b[38;2;254;182;45;48;2;255;170;10m▄\x1b[0m\x1b[38;2;254;205;45;48;2;255;197;10m▄\x1b[0m\x1b[38;2;254;228;45;48;2;255;223;10m▄\x1b[0m\x1b[38;2;254;251;45;48;2;255;250;10m▄\x1b[0m\x1b[38;2;235;254;45;48;2;232;255;10m▄\x1b[0m\x1b[38;2;213;254;45;48;2;206;255;10m▄\x1b[0m\x1b[38;2;190;254;45;48;2;179;255;10m▄\x1b[0m\x1b[38;2;167;254;45;48;2;152;255;10m▄\x1b[0m\x1b[38;2;144;254;45;48;2;125;255;10m▄\x1b[0m\x1b[38;2;121;254;45;48;2;99;255;10m▄\x1b[0m\x1b[38;2;99;254;45;48;2;72;255;10m▄\x1b[0m\x1b[38;2;76;254;45;48;2;45;255;10m▄\x1b[0m\x1b[38;2;53;254;45;48;2;19;255;10m▄\x1b[0m\x1b[38;2;45;254;61;48;2;10;255;28m▄\x1b[0m\x1b[38;2;45;254;83;48;2;10;255;54m▄\x1b[0m\x1b[38;2;45;254;106;48;2;10;255;81m▄\x1b[0m\x1b[38;2;45;254;129;48;2;10;255;108m▄\x1b[0m\x1b[38;2;45;254;152;48;2;10;255;134m▄\x1b[0m\x1b[38;2;45;254;175;48;2;10;255;161m▄\x1b[0m\x1b[38;2;45;254;197;48;2;10;255;188m▄\x1b[0m\x1b[38;2;45;254;220;48;2;10;255;214m▄\x1b[0m\x1b[38;2;45;254;243;48;2;10;255;241m▄\x1b[0m\x1b[38;2;45;243;254;48;2;10;241;255m▄\x1b[0m\x1b[38;2;45;220;254;48;2;10;214;255m▄\x1b[0m\x1b[38;2;45;197;254;48;2;10;188;255m▄\x1b[0m\x1b[38;2;45;175;254;48;2;10;161;255m▄\x1b[0m\x1b[38;2;45;152;254;48;2;10;134;255m▄\x1b[0m\x1b[38;2;45;129;254;48;2;10;108;255m▄\x1b[0m\x1b[38;2;45;106;254;48;2;10;81;255m▄\x1b[0m\x1b[38;2;45;83;254;48;2;10;54;255m▄\x1b[0m\x1b[38;2;45;61;254;48;2;10;28;255m▄\x1b[0m\x1b[38;2;53;45;254;48;2;19;10;255m▄\x1b[0m\x1b[38;2;76;45;254;48;2;45;10;255m▄\x1b[0m\x1b[38;2;99;45;254;48;2;72;10;255m▄\x1b[0m\x1b[38;2;121;45;254;48;2;99;10;255m▄\x1b[0m\x1b[38;2;144;45;254;48;2;125;10;255m▄\x1b[0m\x1b[38;2;167;45;254;48;2;152;10;255m▄\x1b[0m\x1b[38;2;190;45;254;48;2;179;10;255m▄\x1b[0m\x1b[38;2;213;45;254;48;2;206;10;255m▄\x1b[0m\x1b[38;2;235;45;254;48;2;232;10;255m▄\x1b[0m\x1b[38;2;254;45;251;48;2;255;10;250m▄\x1b[0m\x1b[38;2;254;45;228;48;2;255;10;223m▄\x1b[0m\x1b[38;2;254;45;205;48;2;255;10;197m▄\x1b[0m\x1b[38;2;254;45;182;48;2;255;10;170m▄\x1b[0m\x1b[38;2;254;45;159;48;2;255;10;143m▄\x1b[0m\x1b[38;2;254;45;137;48;2;255;10;117m▄\x1b[0m\x1b[38;2;254;45;114;48;2;255;10;90m▄\x1b[0m\x1b[38;2;254;45;91;48;2;255;10;63m▄\x1b[0m\x1b[38;2;254;45;68;48;2;255;10;36m▄\x1b[0m \n\x1b[1;31m \x1b[0m✓ \x1b[1;36mAutomatic color conversion\x1b[0m \x1b[38;2;255;117;117;48;2;255;81;81m▄\x1b[0m\x1b[38;2;255;132;117;48;2;255;100;81m▄\x1b[0m\x1b[38;2;255;147;117;48;2;255;119;81m▄\x1b[0m\x1b[38;2;255;162;117;48;2;255;138;81m▄\x1b[0m\x1b[38;2;255;177;117;48;2;255;157;81m▄\x1b[0m\x1b[38;2;255;192;117;48;2;255;176;81m▄\x1b[0m\x1b[38;2;255;207;117;48;2;255;195;81m▄\x1b[0m\x1b[38;2;255;222;117;48;2;255;214;81m▄\x1b[0m\x1b[38;2;255;237;117;48;2;255;232;81m▄\x1b[0m\x1b[38;2;255;252;117;48;2;255;251;81m▄\x1b[0m\x1b[38;2;242;255;117;48;2;239;255;81m▄\x1b[0m\x1b[38;2;227;255;117;48;2;220;255;81m▄\x1b[0m\x1b[38;2;212;255;117;48;2;201;255;81m▄\x1b[0m\x1b[38;2;197;255;117;48;2;182;255;81m▄\x1b[0m\x1b[38;2;182;255;117;48;2;163;255;81m▄\x1b[0m\x1b[38;2;167;255;117;48;2;144;255;81m▄\x1b[0m\x1b[38;2;152;255;117;48;2;125;255;81m▄\x1b[0m\x1b[38;2;137;255;117;48;2;106;255;81m▄\x1b[0m\x1b[38;2;122;255;117;48;2;87;255;81m▄\x1b[0m\x1b[38;2;117;255;127;48;2;81;255;94m▄\x1b[0m\x1b[38;2;117;255;142;48;2;81;255;113m▄\x1b[0m\x1b[38;2;117;255;157;48;2;81;255;132m▄\x1b[0m\x1b[38;2;117;255;172;48;2;81;255;150m▄\x1b[0m\x1b[38;2;117;255;187;48;2;81;255;169m▄\x1b[0m\x1b[38;2;117;255;202;48;2;81;255;188m▄\x1b[0m\x1b[38;2;117;255;217;48;2;81;255;207m▄\x1b[0m\x1b[38;2;117;255;232;48;2;81;255;226m▄\x1b[0m\x1b[38;2;117;255;247;48;2;81;255;245m▄\x1b[0m\x1b[38;2;117;247;255;48;2;81;245;255m▄\x1b[0m\x1b[38;2;117;232;255;48;2;81;226;255m▄\x1b[0m\x1b[38;2;117;217;255;48;2;81;207;255m▄\x1b[0m\x1b[38;2;117;202;255;48;2;81;188;255m▄\x1b[0m\x1b[38;2;117;187;255;48;2;81;169;255m▄\x1b[0m\x1b[38;2;117;172;255;48;2;81;150;255m▄\x1b[0m\x1b[38;2;117;157;255;48;2;81;132;255m▄\x1b[0m\x1b[38;2;117;142;255;48;2;81;113;255m▄\x1b[0m\x1b[38;2;117;127;255;48;2;81;94;255m▄\x1b[0m\x1b[38;2;122;117;255;48;2;87;81;255m▄\x1b[0m\x1b[38;2;137;117;255;48;2;106;81;255m▄\x1b[0m\x1b[38;2;152;117;255;48;2;125;81;255m▄\x1b[0m\x1b[38;2;167;117;255;48;2;144;81;255m▄\x1b[0m\x1b[38;2;182;117;255;48;2;163;81;255m▄\x1b[0m\x1b[38;2;197;117;255;48;2;182;81;255m▄\x1b[0m\x1b[38;2;212;117;255;48;2;201;81;255m▄\x1b[0m\x1b[38;2;227;117;255;48;2;220;81;255m▄\x1b[0m\x1b[38;2;242;117;255;48;2;239;81;255m▄\x1b[0m\x1b[38;2;255;117;252;48;2;255;81;251m▄\x1b[0m\x1b[38;2;255;117;237;48;2;255;81;232m▄\x1b[0m\x1b[38;2;255;117;222;48;2;255;81;214m▄\x1b[0m\x1b[38;2;255;117;207;48;2;255;81;195m▄\x1b[0m\x1b[38;2;255;117;192;48;2;255;81;176m▄\x1b[0m\x1b[38;2;255;117;177;48;2;255;81;157m▄\x1b[0m\x1b[38;2;255;117;162;48;2;255;81;138m▄\x1b[0m\x1b[38;2;255;117;147;48;2;255;81;119m▄\x1b[0m\x1b[38;2;255;117;132;48;2;255;81;100m▄\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Styles \x1b[0m\x1b[1;31m \x1b[0mAll ansi styles: \x1b[1mbold\x1b[0m, \x1b[2mdim\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[4munderline\x1b[0m, \x1b[9mstrikethrough\x1b[0m, \x1b[7mreverse\x1b[0m, and even \n\x1b[1;31m \x1b[0m\x1b[5mblink\x1b[0m. \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Text \x1b[0m\x1b[1;31m \x1b[0mWord wrap text. Justify \x1b[32mleft\x1b[0m, \x1b[33mcenter\x1b[0m, \x1b[34mright\x1b[0m or \x1b[31mfull\x1b[0m. \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32mLorem ipsum dolor \x1b[0m \x1b[33m Lorem ipsum dolor \x1b[0m \x1b[34m Lorem ipsum dolor\x1b[0m \x1b[31mLorem\x1b[0m\x1b[31m \x1b[0m\x1b[31mipsum\x1b[0m\x1b[31m \x1b[0m\x1b[31mdolor\x1b[0m\x1b[31m \x1b[0m\x1b[31msit\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32msit amet, \x1b[0m \x1b[33m sit amet, \x1b[0m \x1b[34m sit amet,\x1b[0m \x1b[31mamet,\x1b[0m\x1b[31m \x1b[0m\x1b[31mconsectetur\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32mconsectetur \x1b[0m \x1b[33m consectetur \x1b[0m \x1b[34m consectetur\x1b[0m \x1b[31madipiscing\x1b[0m\x1b[31m \x1b[0m\x1b[31melit.\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32madipiscing elit. \x1b[0m \x1b[33m adipiscing elit. \x1b[0m \x1b[34m adipiscing elit.\x1b[0m \x1b[31mQuisque\x1b[0m\x1b[31m \x1b[0m\x1b[31min\x1b[0m\x1b[31m \x1b[0m\x1b[31mmetus\x1b[0m\x1b[31m \x1b[0m\x1b[31msed\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32mQuisque in metus sed\x1b[0m \x1b[33mQuisque in metus sed\x1b[0m \x1b[34mQuisque in metus sed\x1b[0m \x1b[31msapien\x1b[0m\x1b[31m \x1b[0m\x1b[31multricies\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32msapien ultricies \x1b[0m \x1b[33m sapien ultricies \x1b[0m \x1b[34m sapien ultricies\x1b[0m \x1b[31mpretium\x1b[0m\x1b[31m \x1b[0m\x1b[31ma\x1b[0m\x1b[31m \x1b[0m\x1b[31mat\x1b[0m\x1b[31m \x1b[0m\x1b[31mjusto.\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32mpretium a at justo. \x1b[0m \x1b[33mpretium a at justo. \x1b[0m \x1b[34m pretium a at justo.\x1b[0m \x1b[31mMaecenas\x1b[0m\x1b[31m \x1b[0m\x1b[31mluctus\x1b[0m\x1b[31m \x1b[0m\x1b[31mvelit\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32mMaecenas luctus \x1b[0m \x1b[33m Maecenas luctus \x1b[0m \x1b[34m Maecenas luctus\x1b[0m \x1b[31met auctor maximus.\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32mvelit et auctor \x1b[0m \x1b[33m velit et auctor \x1b[0m \x1b[34m velit et auctor\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32mmaximus. \x1b[0m \x1b[33m maximus. \x1b[0m \x1b[34m maximus.\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Asian \x1b[0m\x1b[1;31m \x1b[0m🇨🇳 该库支持中文,日文和韩文文本! \n\x1b[1;31m \x1b[0m\x1b[1;31m language \x1b[0m\x1b[1;31m \x1b[0m🇯🇵 ライブラリは中国語、日本語、韓国語のテキストをサポートしています \n\x1b[1;31m \x1b[0m\x1b[1;31m support \x1b[0m\x1b[1;31m \x1b[0m🇰🇷 이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다 \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Markup \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;35mRich\x1b[0m supports a simple \x1b[3mbbcode\x1b[0m-like \x1b[1mmarkup\x1b[0m for \x1b[33mcolor\x1b[0m, \x1b[4mstyle\x1b[0m, and emoji! 👍 🍎 🐜 🐻 … \n\x1b[1;31m \x1b[0m🚌 \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Tables \x1b[0m\x1b[1;31m \x1b[0m\x1b[1m \x1b[0m\x1b[1;32mDate\x1b[0m\x1b[1m \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1;34mTitle\x1b[0m\x1b[1m \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1;36mProduction Budget\x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1m \x1b[0m\x1b[1;35mBox Office\x1b[0m\x1b[1m \x1b[0m \n\x1b[1;31m \x1b[0m───────────────────────────────────────────────────────────────────────────────────── \n\x1b[1;31m \x1b[0m\x1b[32m \x1b[0m\x1b[32mDec 20, 2019\x1b[0m\x1b[32m \x1b[0m \x1b[34m \x1b[0m\x1b[34mStar Wars: The Rise of \x1b[0m\x1b[34m \x1b[0m \x1b[36m \x1b[0m\x1b[36m $275,000,000\x1b[0m\x1b[36m \x1b[0m \x1b[35m \x1b[0m\x1b[35m $375,126,118\x1b[0m\x1b[35m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32m \x1b[0m \x1b[34m \x1b[0m\x1b[34mSkywalker \x1b[0m\x1b[34m \x1b[0m \x1b[36m \x1b[0m \x1b[35m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[2;32m \x1b[0m\x1b[2;32mMay 25, 2018\x1b[0m\x1b[2;32m \x1b[0m \x1b[2;34m \x1b[0m\x1b[1;2;34mSolo\x1b[0m\x1b[2;34m: A Star Wars Story \x1b[0m\x1b[2;34m \x1b[0m \x1b[2;36m \x1b[0m\x1b[2;36m $275,000,000\x1b[0m\x1b[2;36m \x1b[0m \x1b[2;35m \x1b[0m\x1b[2;35m $393,151,347\x1b[0m\x1b[2;35m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32m \x1b[0m\x1b[32mDec 15, 2017\x1b[0m\x1b[32m \x1b[0m \x1b[34m \x1b[0m\x1b[34mStar Wars Ep. VIII: The Last \x1b[0m\x1b[34m \x1b[0m \x1b[36m \x1b[0m\x1b[36m $262,000,000\x1b[0m\x1b[36m \x1b[0m \x1b[35m \x1b[0m\x1b[1;35m$1,332,539,889\x1b[0m\x1b[35m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[32m \x1b[0m \x1b[34m \x1b[0m\x1b[34mJedi \x1b[0m\x1b[34m \x1b[0m \x1b[36m \x1b[0m \x1b[35m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[2;32m \x1b[0m\x1b[2;32mMay 19, 1999\x1b[0m\x1b[2;32m \x1b[0m \x1b[2;34m \x1b[0m\x1b[2;34mStar Wars Ep. \x1b[0m\x1b[1;2;34mI\x1b[0m\x1b[2;34m: \x1b[0m\x1b[2;3;34mThe phantom \x1b[0m\x1b[2;34m \x1b[0m\x1b[2;34m \x1b[0m \x1b[2;36m \x1b[0m\x1b[2;36m $115,000,000\x1b[0m\x1b[2;36m \x1b[0m \x1b[2;35m \x1b[0m\x1b[2;35m$1,027,044,677\x1b[0m\x1b[2;35m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[2;32m \x1b[0m \x1b[2;34m \x1b[0m\x1b[2;3;34mMenace\x1b[0m\x1b[2;34m \x1b[0m\x1b[2;34m \x1b[0m \x1b[2;36m \x1b[0m \x1b[2;35m \x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Syntax \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 1 \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mdef\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34miter_last\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mIterable\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m[\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mT\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m]\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m-\x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m>\x1b[0m \x1b[1m{\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31mhighlighting\x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 2 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;230;219;116;48;2;39;40;34m\"\"\"Iterate and generate a tuple w\x1b[0m \x1b[2;32m│ \x1b[0m\x1b[32m'foo'\x1b[0m: \x1b[1m[\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m & \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 3 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1;36m3.1427\x1b[0m, \n\x1b[1;31m \x1b[0m\x1b[1;31m pretty \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 4 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mtry\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1m(\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m printing \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 5 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mnext\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_va\x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m'Paul Atreides'\x1b[0m, \n\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 6 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mexcept\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34mStopIteration\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m'Vladimir Harkonnen'\x1b[0m, \n\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 7 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mreturn\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m'Thufir Hawat'\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 8 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mfor\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34min\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1m)\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 9 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mFalse\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ \x1b[0m\x1b[1m]\x1b[0m, \n\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m10 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ \x1b[0m\x1b[32m'atomic'\x1b[0m: \x1b[1m(\x1b[0m\x1b[3;91mFalse\x1b[0m, \x1b[3;92mTrue\x1b[0m, \x1b[3;35mNone\x1b[0m\x1b[1m)\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m11 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mTrue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[1m}\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Markdown \x1b[0m\x1b[1;31m \x1b[0m\x1b[36m# Markdown\x1b[0m ╔═══════════════════════════════════════╗ \n\x1b[1;31m \x1b[0m ║ \x1b[1mMarkdown\x1b[0m ║ \n\x1b[1;31m \x1b[0m\x1b[36mSupports much of the *markdown* \x1b[0m ╚═══════════════════════════════════════╝ \n\x1b[1;31m \x1b[0m\x1b[36m__syntax__!\x1b[0m \n\x1b[1;31m \x1b[0m Supports much of the \x1b[3mmarkdown\x1b[0m \x1b[1msyntax\x1b[0m! \n\x1b[1;31m \x1b[0m\x1b[36m- Headers\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[36m- Basic formatting: **bold**, *italic*, \x1b[0m \x1b[1;33m • \x1b[0mHeaders \n\x1b[1;31m \x1b[0m\x1b[36m`code`\x1b[0m \x1b[1;33m • \x1b[0mBasic formatting: \x1b[1mbold\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[97;40mcode\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[36m- Block quotes\x1b[0m \x1b[1;33m • \x1b[0mBlock quotes \n\x1b[1;31m \x1b[0m\x1b[36m- Lists, and more...\x1b[0m \x1b[1;33m • \x1b[0mLists, and more... \n\x1b[1;31m \x1b[0m\x1b[36m \x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m +more! \x1b[0m\x1b[1;31m \x1b[0mProgress bars, columns, styled logging handler, tracebacks, etc... \n\x1b[1;31m \x1b[0m \n" diff --git a/tests/test_cells.py b/tests/test_cells.py index 06de437202..d64317f3ba 100644 --- a/tests/test_cells.py +++ b/tests/test_cells.py @@ -2,6 +2,10 @@ def test_set_cell_size(): + assert cells.set_cell_size("foo", 0) == "" + assert cells.set_cell_size("f", 0) == "" + assert cells.set_cell_size("", 0) == "" + assert cells.set_cell_size("😽😽", 0) == "" assert cells.set_cell_size("foo", 2) == "fo" assert cells.set_cell_size("foo", 3) == "foo" assert cells.set_cell_size("foo", 4) == "foo " @@ -9,3 +13,16 @@ def test_set_cell_size(): assert cells.set_cell_size("😽😽", 3) == "😽 " assert cells.set_cell_size("😽😽", 2) == "😽" assert cells.set_cell_size("😽😽", 1) == " " + assert cells.set_cell_size("😽😽", 5) == "😽😽 " + + +def test_set_cell_size_infinite(): + for size in range(38): + assert ( + cells.cell_len( + cells.set_cell_size( + "เป็นเกมที่ต้องมีความอดทนมากที่สุดตั้งเเต่เคยเล่นมา", size + ) + ) + == size + ) diff --git a/tests/test_columns.py b/tests/test_columns.py index 927aba2116..1b167f4acb 100644 --- a/tests/test_columns.py +++ b/tests/test_columns.py @@ -41,7 +41,7 @@ def render(): console.rule("optimal, expand") columns.expand = True console.print(columns) - console.rule("columm first, optimal") + console.rule("column first, optimal") columns.column_first = True columns.expand = False console.print(columns) @@ -62,7 +62,7 @@ def render(): def test_render(): - expected = "────────────────────────────────────────────── empty ───────────────────────────────────────────────\n───────────────────────────────────────────── optimal ──────────────────────────────────────────────\nUrsus americanus American buffalo Bison bison American crow \nCorvus brachyrhynchos American marten Martes americana American racer \nColuber constrictor American woodcock Scolopax minor Anaconda (unidentified)\nEunectes sp. Andean goose Chloephaga melanoptera Ant \nAnteater, australian spiny Tachyglossus aculeatus Anteater, giant Myrmecophaga tridactyla\n───────────────────────────────────────── optimal, expand ──────────────────────────────────────────\nUrsus americanus American buffalo Bison bison American crow \nCorvus brachyrhynchos American marten Martes americana American racer \nColuber constrictor American woodcock Scolopax minor Anaconda (unidentified)\nEunectes sp. Andean goose Chloephaga melanoptera Ant \nAnteater, australian spiny Tachyglossus aculeatus Anteater, giant Myrmecophaga tridactyla\n────────────────────────────────────── columm first, optimal ───────────────────────────────────────\nUrsus americanus American marten Scolopax minor Ant \nAmerican buffalo Martes americana Anaconda (unidentified) Anteater, australian spiny\nBison bison American racer Eunectes sp. Tachyglossus aculeatus \nAmerican crow Coluber constrictor Andean goose Anteater, giant \nCorvus brachyrhynchos American woodcock Chloephaga melanoptera Myrmecophaga tridactyla \n─────────────────────────────────── column first, right to left ────────────────────────────────────\nAnt Scolopax minor American marten Ursus americanus \nAnteater, australian spiny Anaconda (unidentified) Martes americana American buffalo \nTachyglossus aculeatus Eunectes sp. American racer Bison bison \nAnteater, giant Andean goose Coluber constrictor American crow \nMyrmecophaga tridactyla Chloephaga melanoptera American woodcock Corvus brachyrhynchos\n────────────────────────────────────── equal columns, expand ───────────────────────────────────────\nChloephaga melanoptera American racer Ursus americanus \nAnt Coluber constrictor American buffalo \nAnteater, australian spiny American woodcock Bison bison \nTachyglossus aculeatus Scolopax minor American crow \nAnteater, giant Anaconda (unidentified) Corvus brachyrhynchos \nMyrmecophaga tridactyla Eunectes sp. American marten \n Andean goose Martes americana \n─────────────────────────────────────────── fixed width ────────────────────────────────────────────\nAnteater, Eunectes sp. Coluber Corvus Ursus americanus \naustralian spiny constrictor brachyrhynchos \nTachyglossus Andean goose American American marten American buffalo \naculeatus woodcock \nAnteater, giant Chloephaga Scolopax minor Martes americana Bison bison \n melanoptera \nMyrmecophaga Ant Anaconda American racer American crow \ntridactyla (unidentified) \n\n" + expected = "────────────────────────────────────────────── empty ───────────────────────────────────────────────\n───────────────────────────────────────────── optimal ──────────────────────────────────────────────\nUrsus americanus American buffalo Bison bison American crow \nCorvus brachyrhynchos American marten Martes americana American racer \nColuber constrictor American woodcock Scolopax minor Anaconda (unidentified)\nEunectes sp. Andean goose Chloephaga melanoptera Ant \nAnteater, australian spiny Tachyglossus aculeatus Anteater, giant Myrmecophaga tridactyla\n───────────────────────────────────────── optimal, expand ──────────────────────────────────────────\nUrsus americanus American buffalo Bison bison American crow \nCorvus brachyrhynchos American marten Martes americana American racer \nColuber constrictor American woodcock Scolopax minor Anaconda (unidentified)\nEunectes sp. Andean goose Chloephaga melanoptera Ant \nAnteater, australian spiny Tachyglossus aculeatus Anteater, giant Myrmecophaga tridactyla\n────────────────────────────────────── column first, optimal ───────────────────────────────────────\nUrsus americanus American marten Scolopax minor Ant \nAmerican buffalo Martes americana Anaconda (unidentified) Anteater, australian spiny\nBison bison American racer Eunectes sp. Tachyglossus aculeatus \nAmerican crow Coluber constrictor Andean goose Anteater, giant \nCorvus brachyrhynchos American woodcock Chloephaga melanoptera Myrmecophaga tridactyla \n─────────────────────────────────── column first, right to left ────────────────────────────────────\nAnt Scolopax minor American marten Ursus americanus \nAnteater, australian spiny Anaconda (unidentified) Martes americana American buffalo \nTachyglossus aculeatus Eunectes sp. American racer Bison bison \nAnteater, giant Andean goose Coluber constrictor American crow \nMyrmecophaga tridactyla Chloephaga melanoptera American woodcock Corvus brachyrhynchos\n────────────────────────────────────── equal columns, expand ───────────────────────────────────────\nChloephaga melanoptera American racer Ursus americanus \nAnt Coluber constrictor American buffalo \nAnteater, australian spiny American woodcock Bison bison \nTachyglossus aculeatus Scolopax minor American crow \nAnteater, giant Anaconda (unidentified) Corvus brachyrhynchos \nMyrmecophaga tridactyla Eunectes sp. American marten \n Andean goose Martes americana \n─────────────────────────────────────────── fixed width ────────────────────────────────────────────\nAnteater, Eunectes sp. Coluber Corvus Ursus americanus \naustralian spiny constrictor brachyrhynchos \nTachyglossus Andean goose American American marten American buffalo \naculeatus woodcock \nAnteater, giant Chloephaga Scolopax minor Martes americana Bison bison \n melanoptera \nMyrmecophaga Ant Anaconda American racer American crow \ntridactyla (unidentified) \n\n" assert render() == expected diff --git a/tests/test_console.py b/tests/test_console.py index 061cbd7faf..0989d8ba16 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -87,6 +87,23 @@ def test_console_options_update(): assert options_copy == options and options_copy is not options +def test_console_options_update_height(): + options = ConsoleOptions( + ConsoleDimensions(80, 25), + max_height=25, + legacy_windows=False, + min_width=10, + max_width=20, + is_terminal=False, + encoding="utf-8", + ) + assert options.height is None + render_options = options.update_height(12) + assert options.height is None + assert render_options.height == 12 + assert render_options.max_height == 12 + + def test_init(): console = Console(color_system=None) assert console._color_system == None @@ -100,7 +117,7 @@ def test_size(): w, h = console.size assert console.width == w - console = Console(width=99, height=101) + console = Console(width=99, height=101, legacy_windows=False) w, h = console.size assert w == 99 and h == 101 @@ -117,6 +134,24 @@ def test_print(): assert console.file.getvalue() == "foo\n" +def test_print_multiple(): + console = Console(file=io.StringIO(), color_system="truecolor") + console.print("foo", "bar") + assert console.file.getvalue() == "foo bar\n" + + +def test_print_text(): + console = Console(file=io.StringIO(), color_system="truecolor") + console.print(Text("foo", style="bold")) + assert console.file.getvalue() == "\x1B[1mfoo\x1B[0m\n" + + +def test_print_text_multiple(): + console = Console(file=io.StringIO(), color_system="truecolor") + console.print(Text("foo", style="bold"), Text("bar"), "baz") + assert console.file.getvalue() == "\x1B[1mfoo\x1B[0m bar baz\n" + + def test_print_json(): console = Console(file=io.StringIO(), color_system="truecolor") console.print_json('[false, true, null, "foo"]', indent=4) @@ -141,6 +176,15 @@ def test_print_json_data(): assert result == expected +def test_print_json_ensure_ascii(): + console = Console(file=io.StringIO(), color_system="truecolor") + console.print_json(data={"foo": "💩"}, ensure_ascii=False) + result = console.file.getvalue() + print(repr(result)) + expected = '\x1b[1m{\x1b[0m\n \x1b[1;34m"foo"\x1b[0m: \x1b[32m"💩"\x1b[0m\n\x1b[1m}\x1b[0m\n' + assert result == expected + + def test_log(): console = Console( file=io.StringIO(), @@ -598,13 +642,13 @@ def test_height(): def test_columns_env(): - console = Console(_environ={"COLUMNS": "314"}) + console = Console(_environ={"COLUMNS": "314"}, legacy_windows=False) assert console.width == 314 # width take precedence - console = Console(width=40, _environ={"COLUMNS": "314"}) + console = Console(width=40, _environ={"COLUMNS": "314"}, legacy_windows=False) assert console.width == 40 # Should not fail - console = Console(width=40, _environ={"COLUMNS": "broken"}) + console = Console(width=40, _environ={"COLUMNS": "broken"}, legacy_windows=False) def test_lines_env(): @@ -642,7 +686,7 @@ def test_is_alt_screen(): def test_update_screen(): - console = Console(force_terminal=True, width=20, height=5) + console = Console(force_terminal=True, width=20, height=5, _environ={}) if console.legacy_windows: return with pytest.raises(errors.NoAltScreen): @@ -680,7 +724,7 @@ def test_print_width_zero(): def test_size_properties(): - console = Console(width=80, height=25) + console = Console(width=80, height=25, legacy_windows=False) assert console.size == ConsoleDimensions(80, 25) console.size = (10, 20) assert console.size == ConsoleDimensions(10, 20) @@ -698,3 +742,20 @@ def test_print_newline_start(): result = console.end_capture() assert result == "Foo\n\nFoo\nbar\n\n" + + +def test_is_terminal_broken_file(): + console = Console() + + def _mock_isatty(): + raise ValueError() + + console.file.isatty = _mock_isatty + + assert console.is_terminal == False + + +@pytest.mark.skipif(sys.platform == "win32", reason="not relevant on Windows") +def test_detect_color_system(): + console = Console(_environ={"TERM": "rxvt-unicode-256color"}, force_terminal=True) + assert console._detect_color_system() == ColorSystem.EIGHT_BIT diff --git a/tests/test_highlighter.py b/tests/test_highlighter.py index b683312d1e..99793723c8 100644 --- a/tests/test_highlighter.py +++ b/tests/test_highlighter.py @@ -1,4 +1,4 @@ -"""Tests for the higlighter classes.""" +"""Tests for the highlighter classes.""" import pytest from typing import List diff --git a/tests/test_inspect.py b/tests/test_inspect.py index 5a9a57610c..63c5f06244 100644 --- a/tests/test_inspect.py +++ b/tests/test_inspect.py @@ -1,12 +1,12 @@ import io import sys +from types import ModuleType import pytest from rich import inspect from rich.console import Console - skip_py36 = pytest.mark.skipif( sys.version_info.minor == 6 and sys.version_info.major == 3, reason="rendered differently on py3.6", @@ -82,7 +82,6 @@ def test_render(): def test_inspect_text(): - expected = ( "╭──────────────── ─────────────────╮\n" "│ str(object='') -> str │\n" @@ -100,7 +99,6 @@ def test_inspect_text(): @skip_py36 @skip_py37 def test_inspect_empty_dict(): - expected = ( "╭──────────────── ────────────────╮\n" "│ dict() -> new empty dictionary │\n" @@ -122,7 +120,6 @@ def test_inspect_empty_dict(): def test_inspect_builtin_function(): - expected = ( "╭────────── ───────────╮\n" "│ def print(...) │\n" @@ -139,7 +136,6 @@ def test_inspect_builtin_function(): @skip_py36 def test_inspect_integer(): - expected = ( "╭────── ───────╮\n" "│ int([x]) -> integer │\n" @@ -156,7 +152,6 @@ def test_inspect_integer(): @skip_py36 def test_inspect_integer_with_value(): - expected = "╭────── ───────╮\n│ int([x]) -> integer │\n│ int(x, base=10) -> integer │\n│ │\n│ ╭────────────────────────╮ │\n│ │ 1 │ │\n│ ╰────────────────────────╯ │\n│ │\n│ denominator = 1 │\n│ imag = 0 │\n│ numerator = 1 │\n│ real = 1 │\n╰────────────────────────────╯\n" value = render(1, value=True) print(repr(value)) @@ -167,7 +162,6 @@ def test_inspect_integer_with_value(): @skip_py37 @skip_py310 def test_inspect_integer_with_methods(): - expected = ( "╭──────────────── ─────────────────╮\n" "│ int([x]) -> integer │\n" @@ -205,7 +199,6 @@ def test_inspect_integer_with_methods(): @skip_py38 @skip_py39 def test_inspect_integer_with_methods(): - expected = ( "╭──────────────── ─────────────────╮\n" "│ int([x]) -> integer │\n" @@ -260,3 +253,40 @@ class Foo: result = render(foo, methods=True, width=100) print(repr(result)) assert expected == result + + +def test_inspect_swig_edge_case(): + """Issue #1838 - Edge case with Faiss library - object with empty dir()""" + + class Thing: + @property + def __class__(self): + raise AttributeError + + thing = Thing() + try: + inspect(thing) + except Exception as e: + assert False, f"Object with no __class__ shouldn't raise {e}" + + +def test_inspect_module_with_class(): + def function(): + pass + + class Thing: + """Docstring""" + + pass + + module = ModuleType("my_module") + module.SomeClass = Thing + module.function = function + + expected = ( + "╭────────── ──────────╮\n" + "│ function = def function(): │\n" + "│ SomeClass = class SomeClass(): Docstring │\n" + "╰──────────────────────────────────────────╯\n" + ) + assert render(module, methods=True) == expected diff --git a/tests/test_json.py b/tests/test_json.py new file mode 100644 index 0000000000..7571b0a393 --- /dev/null +++ b/tests/test_json.py @@ -0,0 +1,8 @@ +from rich.json import JSON +import datetime + + +def test_print_json_data_with_default(): + date = datetime.date(2021, 1, 1) + json = JSON.from_data({"date": date}, default=lambda d: d.isoformat()) + assert str(json.text) == '{\n "date": "2021-01-01"\n}' diff --git a/tests/test_layout.py b/tests/test_layout.py index 21d30d27f6..61cc9b6b5d 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -86,7 +86,7 @@ def test_tree(): def test_refresh_screen(): layout = Layout() layout.split_row(Layout(name="foo"), Layout(name="bar")) - console = Console(force_terminal=True, width=20, height=5) + console = Console(force_terminal=True, width=20, height=5, _environ={}) with console.capture(): console.print(layout) with console.screen(): diff --git a/tests/test_log.py b/tests/test_log.py index 84da7a4458..22bb8b7b83 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -6,7 +6,6 @@ from rich.console import Console - re_link_ids = re.compile(r"id=[\d\.\-]*?;.*?\x1b") @@ -33,12 +32,12 @@ def render_log(): console.log() console.log("Hello from", console, "!") console.log(test_data, log_locals=True) - return replace_link_ids(console.file.getvalue()) + return replace_link_ids(console.file.getvalue()).replace("test_log.py", "source.py") def test_log(): expected = replace_link_ids( - "\x1b[2;36m[TIME]\x1b[0m\x1b[2;36m \x1b[0m \x1b]8;id=0;foo\x1b\\\x1b[2mtest_log.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:33\x1b[0m\n\x1b[2;36m \x1b[0m\x1b[2;36m \x1b[0mHello from \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;36m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m ! \x1b]8;id=0;foo\x1b\\\x1b[2mtest_log.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:34\x1b[0m\n\x1b[2;36m \x1b[0m\x1b[2;36m \x1b[0m\x1b[1m[\x1b[0m\x1b[1;36m1\x1b[0m, \x1b[1;36m2\x1b[0m, \x1b[1;36m3\x1b[0m\x1b[1m]\x1b[0m \x1b]8;id=0;foo\x1b\\\x1b[2mtest_log.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:35\x1b[0m\n \x1b[34m╭─\x1b[0m\x1b[34m───────────────────── \x1b[0m\x1b[3;34mlocals\x1b[0m\x1b[34m ─────────────────────\x1b[0m\x1b[34m─╮\x1b[0m \n \x1b[34m│\x1b[0m \x1b[3;33mconsole\x1b[0m\x1b[31m =\x1b[0m \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;36m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m \x1b[34m│\x1b[0m \n \x1b[34m╰────────────────────────────────────────────────────╯\x1b[0m \n" + "\x1b[2;36m[TIME]\x1b[0m\x1b[2;36m \x1b[0m \x1b]8;id=0;foo\x1b\\\x1b[2msource.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:\x1b[0m\x1b]8;id=0;foo\x1b\\\x1b[2m32\x1b[0m\x1b]8;;\x1b\\\n\x1b[2;36m \x1b[0m\x1b[2;36m \x1b[0mHello from \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;36m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m ! \x1b]8;id=0;foo\x1b\\\x1b[2msource.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:\x1b[0m\x1b]8;id=0;foo\x1b\\\x1b[2m33\x1b[0m\x1b]8;;\x1b\\\n\x1b[2;36m \x1b[0m\x1b[2;36m \x1b[0m\x1b[1m[\x1b[0m\x1b[1;36m1\x1b[0m, \x1b[1;36m2\x1b[0m, \x1b[1;36m3\x1b[0m\x1b[1m]\x1b[0m \x1b]8;id=0;foo\x1b\\\x1b[2msource.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:\x1b[0m\x1b]8;id=0;foo\x1b\\\x1b[2m34\x1b[0m\x1b]8;;\x1b\\\n\x1b[2;36m \x1b[0m\x1b[34m╭─\x1b[0m\x1b[34m───────────────────── \x1b[0m\x1b[3;34mlocals\x1b[0m\x1b[34m ─────────────────────\x1b[0m\x1b[34m─╮\x1b[0m \x1b[2m \x1b[0m\n\x1b[2;36m \x1b[0m\x1b[34m│\x1b[0m \x1b[3;33mconsole\x1b[0m\x1b[31m =\x1b[0m \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;36m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m \x1b[34m│\x1b[0m \x1b[2m \x1b[0m\n\x1b[2;36m \x1b[0m\x1b[34m╰────────────────────────────────────────────────────╯\x1b[0m \x1b[2m \x1b[0m\n" ) rendered = render_log() print(repr(rendered)) diff --git a/tests/test_logging.py b/tests/test_logging.py index 59d2ecb25f..ce3ac25af9 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -1,6 +1,8 @@ import io import os import logging +from typing import Optional + import pytest from rich.console import Console @@ -89,6 +91,77 @@ def test_exception_with_extra_lines(): assert "division by zero" in render +def test_stderr_and_stdout_are_none(monkeypatch): + # This test is specifically to handle cases when using pythonw on + # windows and stderr and stdout are set to None. + # See https://bugs.python.org/issue13807 + + monkeypatch.setattr("sys.stdout", None) + monkeypatch.setattr("sys.stderr", None) + + console = Console(_environ={}) + target_handler = RichHandler(console=console) + actual_record: Optional[logging.LogRecord] = None + + def mock_handle_error(record): + nonlocal actual_record + actual_record = record + + target_handler.handleError = mock_handle_error + log.addHandler(target_handler) + + try: + 1 / 0 + except ZeroDivisionError: + log.exception("message") + + finally: + log.removeHandler(target_handler) + + assert actual_record is not None + assert "message" in actual_record.msg + + +def test_markup_and_highlight(): + console = Console( + file=io.StringIO(), + force_terminal=True, + width=140, + color_system="truecolor", + _environ={}, + ) + handler = RichHandler(console=console) + + # Check defaults are as expected + assert handler.highlighter + assert not handler.markup + + formatter = logging.Formatter("FORMATTER %(message)s %(asctime)s") + handler.setFormatter(formatter) + log.addHandler(handler) + + log_message = "foo 3.141 127.0.0.1 [red]alert[/red]" + + log.error(log_message) + render_fancy = handler.console.file.getvalue() + assert "FORMATTER" in render_fancy + assert log_message not in render_fancy + assert "red" in render_fancy + + handler.console.file = io.StringIO() + log.error(log_message, extra={"markup": True}) + render_markup = handler.console.file.getvalue() + assert "FORMATTER" in render_markup + assert log_message not in render_markup + assert "red" not in render_markup + + handler.console.file = io.StringIO() + log.error(log_message, extra={"highlighter": None}) + render_plain = handler.console.file.getvalue() + assert "FORMATTER" in render_plain + assert log_message in render_plain + + if __name__ == "__main__": render = make_log() print(render) diff --git a/tests/test_markup.py b/tests/test_markup.py index 24b0c4417b..5a0acbbf09 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -21,6 +21,9 @@ def test_re_match(): assert RE_TAGS.match("[color(1)]") assert RE_TAGS.match("[#ff00ff]") assert RE_TAGS.match("[/]") + assert RE_TAGS.match("[@]") + assert RE_TAGS.match("[@foo]") + assert RE_TAGS.match("[@foo=bar]") def test_escape(): @@ -32,6 +35,10 @@ def test_escape(): assert escape("[5]") == "[5]" assert escape("\\[5]") == "\\[5]" + # Test @ escape + assert escape("[@foo]") == "\\[@foo]" + assert escape("[@]") == "\\[@]" + def test_render_escape(): console = Console(width=80, color_system=None) diff --git a/tests/test_pretty.py b/tests/test_pretty.py index faf276a1b7..871f1397cc 100644 --- a/tests/test_pretty.py +++ b/tests/test_pretty.py @@ -1,16 +1,16 @@ -from array import array -from collections import defaultdict, UserDict, UserList -from dataclasses import dataclass, field import io import sys +from array import array +from collections import defaultdict, UserDict from typing import List import attr import pytest +from dataclasses import dataclass, field from rich.console import Console -from rich.pretty import install, Pretty, pprint, pretty_repr, Node - +from rich.pretty import install, Pretty, pprint, pretty_repr, Node, _ipy_display_hook +from rich.text import Text skip_py36 = pytest.mark.skipif( sys.version_info.minor == 6 and sys.version_info.major == 3, @@ -44,6 +44,85 @@ def test_install(): assert sys.displayhook is not dh +def test_ipy_display_hook__repr_html(): + console = Console(file=io.StringIO(), force_jupyter=True) + + class Thing: + def _repr_html_(self): + return "hello" + + console.begin_capture() + _ipy_display_hook(Thing(), console=console) + + # Rendering delegated to notebook because _repr_html_ method exists + assert console.end_capture() == "" + + +def test_ipy_display_hook__multiple_special_reprs(): + """ + The case where there are multiple IPython special _repr_*_ + methods on the object, and one of them returns None but another + one does not. + """ + console = Console(file=io.StringIO(), force_jupyter=True) + + class Thing: + def _repr_latex_(self): + return None + + def _repr_html_(self): + return "hello" + + console.begin_capture() + _ipy_display_hook(Thing(), console=console) + + assert console.end_capture() == "" + + +def test_ipy_display_hook__no_special_repr_methods(): + console = Console(file=io.StringIO(), force_jupyter=True) + + class Thing: + def __repr__(self) -> str: + return "hello" + + console.begin_capture() + _ipy_display_hook(Thing(), console=console) + + # No IPython special repr methods, so printed by Rich + assert console.end_capture() == "hello\n" + + +def test_ipy_display_hook__special_repr_raises_exception(): + """ + When an IPython special repr method raises an exception, + we treat it as if it doesn't exist and look for the next. + """ + console = Console(file=io.StringIO(), force_jupyter=True) + + class Thing: + def _repr_markdown_(self): + raise Exception() + + def _repr_latex_(self): + return None + + def _repr_html_(self): + return "hello" + + console.begin_capture() + _ipy_display_hook(Thing(), console=console) + + assert console.end_capture() == "" + + +def test_ipy_display_hook__console_renderables_on_newline(): + console = Console(file=io.StringIO(), force_jupyter=True) + console.begin_capture() + _ipy_display_hook(Text("hello"), console=console) + assert console.end_capture() == "\nhello\n" + + def test_pretty(): test = { "foo": [1, 2, 3, (4, 5, {6}, 7, 8, {9}), {}], @@ -55,7 +134,6 @@ def test_pretty(): result = pretty_repr(test, max_width=80) print(result) - # print(repr(result)) expected = "{\n 'foo': [1, 2, 3, (4, 5, {6}, 7, 8, {9}), {}],\n 'bar': {\n 'egg': 'baz',\n 'words': [\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World'\n ]\n },\n False: 'foo',\n True: '',\n 'text': ('Hello World', 'foo bar baz egg')\n}" print(expected) assert result == expected @@ -67,6 +145,7 @@ class ExampleDataclass: bar: str ignore: int = field(repr=False) baz: List[str] = field(default_factory=list) + last: int = field(default=1, repr=False) def test_pretty_dataclass(): @@ -96,6 +175,7 @@ def test_small_width(): assert result == expected +@skip_py36 def test_broken_repr(): class BrokenRepr: def __repr__(self): @@ -107,6 +187,20 @@ def __repr__(self): assert result == expected +@skip_py36 +def test_broken_getattr(): + class BrokenAttr: + def __getattr__(self, name): + 1 / 0 + + def __repr__(self): + return "BrokenAttr()" + + test = BrokenAttr() + result = pretty_repr(test) + assert result == "BrokenAttr()" + + def test_recursive(): test = [] test.append(test) @@ -115,6 +209,79 @@ def test_recursive(): assert result == expected +def test_max_depth(): + d = {} + d["foo"] = {"fob": {"a": [1, 2, 3], "b": {"z": "x", "y": ["a", "b", "c"]}}} + + assert pretty_repr(d, max_depth=0) == "..." + assert pretty_repr(d, max_depth=1) == "{'foo': ...}" + assert pretty_repr(d, max_depth=2) == "{'foo': {'fob': ...}}" + assert pretty_repr(d, max_depth=3) == "{'foo': {'fob': {'a': ..., 'b': ...}}}" + assert ( + pretty_repr(d, max_width=100, max_depth=4) + == "{'foo': {'fob': {'a': [1, 2, 3], 'b': {'z': 'x', 'y': ...}}}}" + ) + assert ( + pretty_repr(d, max_width=100, max_depth=5) + == "{'foo': {'fob': {'a': [1, 2, 3], 'b': {'z': 'x', 'y': ['a', 'b', 'c']}}}}" + ) + assert ( + pretty_repr(d, max_width=100, max_depth=None) + == "{'foo': {'fob': {'a': [1, 2, 3], 'b': {'z': 'x', 'y': ['a', 'b', 'c']}}}}" + ) + + +def test_max_depth_rich_repr(): + class Foo: + def __init__(self, foo): + self.foo = foo + + def __rich_repr__(self): + yield "foo", self.foo + + class Bar: + def __init__(self, bar): + self.bar = bar + + def __rich_repr__(self): + yield "bar", self.bar + + assert ( + pretty_repr(Foo(foo=Bar(bar=Foo(foo=[]))), max_depth=2) + == "Foo(foo=Bar(bar=...))" + ) + + +def test_max_depth_attrs(): + @attr.define + class Foo: + foo = attr.field() + + @attr.define + class Bar: + bar = attr.field() + + assert ( + pretty_repr(Foo(foo=Bar(bar=Foo(foo=[]))), max_depth=2) + == "Foo(foo=Bar(bar=...))" + ) + + +def test_max_depth_dataclass(): + @dataclass + class Foo: + foo: object + + @dataclass + class Bar: + bar: object + + assert ( + pretty_repr(Foo(foo=Bar(bar=Foo(foo=[]))), max_depth=2) + == "Foo(foo=Bar(bar=...))" + ) + + def test_defaultdict(): test_dict = defaultdict(int, {"foo": 2}) result = pretty_repr(test_dict) diff --git a/tests/test_progress.py b/tests/test_progress.py index 303c2e0871..20b9d32ed4 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -68,6 +68,13 @@ def test_text_column(): assert text == Text("[b]bar") +def test_time_elapsed_column(): + column = TimeElapsedColumn() + task = Task(1, "test", 100, 20, _get_time=lambda: 1.0) + text = column.render(task) + assert str(text) == "-:--:--" + + def test_time_remaining_column(): class FakeTask(Task): time_remaining = 60 @@ -327,6 +334,32 @@ def test_columns() -> None: assert result == expected +def test_using_default_columns() -> None: + # can only check types, as the instances do not '==' each other + expected_default_types = [ + TextColumn, + BarColumn, + TextColumn, + TimeRemainingColumn, + ] + + progress = Progress() + assert [type(c) for c in progress.columns] == expected_default_types + + progress = Progress( + SpinnerColumn(), + *Progress.get_default_columns(), + "Elapsed:", + TimeElapsedColumn(), + ) + assert [type(c) for c in progress.columns] == [ + SpinnerColumn, + *expected_default_types, + str, + TimeElapsedColumn, + ] + + def test_task_create() -> None: task = Task(TaskID(1), "foo", 100, 0, _get_time=lambda: 1) assert task.elapsed is None diff --git a/tests/test_protocol.py b/tests/test_protocol.py index 310b994d3a..6337ef5df6 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -18,6 +18,21 @@ def test_rich_cast(): assert console.file.getvalue() == "Foo\n" +class Fake: + def __getattr__(self, name): + return 12 + + def __repr__(self) -> str: + return "Fake()" + + +def test_rich_cast_fake(): + fake = Fake() + console = Console(file=io.StringIO()) + console.print(fake) + assert console.file.getvalue() == "Fake()\n" + + def test_rich_cast_container(): foo = Foo() console = Console(file=io.StringIO(), legacy_windows=False) @@ -33,3 +48,37 @@ def test_abc(): assert not isinstance(foo, str) assert not isinstance("foo", RichRenderable) assert not isinstance([], RichRenderable) + + +def test_cast_deep(): + class B: + def __rich__(self) -> Foo: + return Foo() + + class A: + def __rich__(self) -> B: + return B() + + console = Console(file=io.StringIO()) + console.print(A()) + assert console.file.getvalue() == "Foo\n" + + +def test_cast_recursive(): + class B: + def __rich__(self) -> "A": + return A() + + def __repr__(self) -> str: + return "" + + class A: + def __rich__(self) -> B: + return B() + + def __repr__(self) -> str: + return "" + + console = Console(file=io.StringIO()) + console.print(A()) + assert console.file.getvalue() == "\n" diff --git a/tests/test_repr.py b/tests/test_repr.py index c4f8bd09aa..3c2f48a462 100644 --- a/tests/test_repr.py +++ b/tests/test_repr.py @@ -1,10 +1,22 @@ import pytest +import sys from typing import Optional from rich.console import Console import rich.repr +skip_py36 = pytest.mark.skipif( + sys.version_info.minor == 6 and sys.version_info.major == 3, + reason="rendered differently on py3.6", +) + +skip_py37 = pytest.mark.skipif( + sys.version_info.minor == 7 and sys.version_info.major == 3, + reason="rendered differently on py3.7", +) + + @rich.repr.auto class Foo: def __init__(self, foo: str, bar: Optional[int] = None, egg: int = 1): @@ -59,6 +71,24 @@ def test_rich_repr() -> None: assert (repr(Foo("hello", bar=3))) == "Foo('hello', 'hello', bar=3, egg=1)" +@skip_py36 +@skip_py37 +def test_rich_repr_positional_only() -> None: + _locals = locals().copy() + exec( + """\ +@rich.repr.auto +class PosOnly: + def __init__(self, foo, /): + self.foo = 1 + """, + globals(), + _locals, + ) + p = _locals["PosOnly"](1) + assert repr(p) == "PosOnly(1)" + + def test_rich_angular() -> None: assert (repr(Bar("hello"))) == "" assert (repr(Bar("hello", bar=3))) == "" diff --git a/tests/test_rich_print.py b/tests/test_rich_print.py index f6ea5110d8..a92b800022 100644 --- a/tests/test_rich_print.py +++ b/tests/test_rich_print.py @@ -1,4 +1,5 @@ import io +import json import rich from rich.console import Console @@ -38,6 +39,27 @@ def test_rich_print_json(): assert result == expected +def test_rich_print_json_round_trip(): + data = ["x" * 100, 2e128] + console = rich.get_console() + with console.capture() as capture: + rich.print_json(data=data, indent=4) + result = capture.get() + print(repr(result)) + result_data = json.loads(result) + assert result_data == data + + +def test_rich_print_json_no_truncation(): + console = rich.get_console() + with console.capture() as capture: + rich.print_json(f'["{"x" * 100}", {int(2e128)}]', indent=4) + result = capture.get() + print(repr(result)) + assert ("x" * 100) in result + assert str(int(2e128)) in result + + def test_rich_print_X(): console = rich.get_console() output = io.StringIO() diff --git a/tests/test_screen.py b/tests/test_screen.py index 7596c3a385..c571e037e0 100644 --- a/tests/test_screen.py +++ b/tests/test_screen.py @@ -3,7 +3,7 @@ def test_screen(): - console = Console(color_system=None, width=20, height=5) + console = Console(color_system=None, width=20, height=5, legacy_windows=False) with console.capture() as capture: console.print(Screen("foo\nbar\nbaz\nfoo\nbar\nbaz\foo")) result = capture.get() diff --git a/tests/test_segment.py b/tests/test_segment.py index c3cb222c35..9adf103299 100644 --- a/tests/test_segment.py +++ b/tests/test_segment.py @@ -1,9 +1,8 @@ -import sys +from io import StringIO import pytest -from rich.segment import ControlType -from rich.segment import Segment, Segments, SegmentLines +from rich.segment import ControlType, Segment, SegmentLines, Segments from rich.style import Style @@ -166,6 +165,34 @@ def test_divide(): ] +# https://github.com/willmcgugan/rich/issues/1755 +def test_divide_complex(): + MAP = ( + "[on orange4] [on green]XX[on orange4] \n" + " \n" + " \n" + " \n" + " [bright_red on black]Y[on orange4] \n" + "[on green]X[on orange4] [on green]X[on orange4] \n" + " [on green]X[on orange4] [on green]X\n" + "[on orange4] \n" + " [on green]XX[on orange4] \n" + ) + from rich.console import Console + from rich.text import Text + + text = Text.from_markup(MAP) + console = Console( + color_system="truecolor", width=30, force_terminal=True, file=StringIO() + ) + console.print(text) + result = console.file.getvalue() + + print(repr(result)) + expected = "\x1b[48;5;94m \x1b[0m\x1b[42mXX\x1b[0m\x1b[48;5;94m \x1b[0m\n\x1b[48;5;94m \x1b[0m\n\x1b[48;5;94m \x1b[0m\n\x1b[48;5;94m \x1b[0m\n\x1b[48;5;94m \x1b[0m\x1b[91;40mY\x1b[0m\x1b[91;48;5;94m \x1b[0m\n\x1b[91;42mX\x1b[0m\x1b[91;48;5;94m \x1b[0m\x1b[91;42mX\x1b[0m\x1b[91;48;5;94m \x1b[0m\n\x1b[91;48;5;94m \x1b[0m\x1b[91;42mX\x1b[0m\x1b[91;48;5;94m \x1b[0m\x1b[91;42mX\x1b[0m\n\x1b[91;48;5;94m \x1b[0m\n\x1b[91;48;5;94m \x1b[0m\x1b[91;42mXX\x1b[0m\x1b[91;48;5;94m \x1b[0m\n\n" + assert result == expected + + def test_divide_emoji(): bold = Style(bold=True) italic = Style(italic=True) @@ -223,6 +250,7 @@ def test_divide_edge_2(): @pytest.mark.parametrize( "text,split,result", [ + ("XX", 4, (Segment("XX"), Segment(""))), ("X", 1, (Segment("X"), Segment(""))), ("💩", 1, (Segment(" "), Segment(" "))), ("XY", 1, (Segment("X"), Segment("Y"))), @@ -270,3 +298,33 @@ def test_segment_lines_renderable(): Segment("foo"), Segment("\n"), ] + + +def test_align_top(): + lines = [[Segment("X")]] + assert Segment.align_top(lines, 3, 1, Style()) == lines + assert Segment.align_top(lines, 3, 3, Style()) == [ + [Segment("X")], + [Segment(" ", Style())], + [Segment(" ", Style())], + ] + + +def test_align_middle(): + lines = [[Segment("X")]] + assert Segment.align_middle(lines, 3, 1, Style()) == lines + assert Segment.align_middle(lines, 3, 3, Style()) == [ + [Segment(" ", Style())], + [Segment("X")], + [Segment(" ", Style())], + ] + + +def test_align_bottom(): + lines = [[Segment("X")]] + assert Segment.align_bottom(lines, 3, 1, Style()) == lines + assert Segment.align_bottom(lines, 3, 3, Style()) == [ + [Segment(" ", Style())], + [Segment(" ", Style())], + [Segment("X")], + ] diff --git a/tests/test_spinner.py b/tests/test_spinner.py index 5560f1daf6..98e3d9d66a 100644 --- a/tests/test_spinner.py +++ b/tests/test_spinner.py @@ -41,7 +41,7 @@ def get_time(): nonlocal time return time - console = Console(width=20, force_terminal=True, get_time=get_time) + console = Console(width=20, force_terminal=True, get_time=get_time, _environ={}) console.begin_capture() spinner = Spinner("dots") console.print(spinner) diff --git a/tests/test_syntax.py b/tests/test_syntax.py index 090d08d250..46d0126e14 100644 --- a/tests/test_syntax.py +++ b/tests/test_syntax.py @@ -1,15 +1,17 @@ # coding=utf-8 +import os import sys -import os, tempfile +import tempfile import pytest -from .render import render +from pygments.lexers import PythonLexer from rich.panel import Panel from rich.style import Style -from rich.syntax import Syntax, ANSISyntaxTheme, PygmentsSyntaxTheme, Color, Console +from rich.syntax import ANSISyntaxTheme, Color, Console, PygmentsSyntaxTheme, Syntax +from .render import render CODE = '''\ def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]: @@ -30,7 +32,7 @@ def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]: def test_blank_lines(): code = "\n\nimport this\n\n" syntax = Syntax( - code, lexer_name="python", theme="ascii_light", code_width=30, line_numbers=True + code, lexer="python", theme="ascii_light", code_width=30, line_numbers=True ) result = render(syntax) print(repr(result)) @@ -44,10 +46,10 @@ def test_python_render(): syntax = Panel.fit( Syntax( CODE, - lexer_name="python", + lexer="python", line_numbers=True, line_range=(2, 10), - theme="foo", + theme="monokai", code_width=60, word_wrap=True, ), @@ -55,29 +57,44 @@ def test_python_render(): ) rendered_syntax = render(syntax) print(repr(rendered_syntax)) - expected = '╭────────────────────────────────────────────────────────────────╮\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 2 \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[3;38;2;186;33;33;48;2;248;248;248m"""Iterate and generate a tuple with a flag for first \x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[48;2;248;248;248m \x1b[0m\x1b[3;38;2;186;33;33;48;2;248;248;248mand last value."""\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 3 \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;128;0;48;2;248;248;248miter\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m(\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalues\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m)\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 4 \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mtry\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 5 \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;128;0;48;2;248;248;248mnext\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m(\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m)\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 6 \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mexcept\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;210;65;58;48;2;248;248;248mStopIteration\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 7 \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mreturn\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 8 \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mTrue\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 9 \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mfor\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalue\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;170;34;255;48;2;248;248;248min\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m10 \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248myield\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mFalse\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n╰────────────────────────────────────────────────────────────────╯\n' + expected = '╭────────────────────────────────────────────────────────────────╮\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 2 \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;230;219;116;48;2;39;40;34m"""Iterate and generate a tuple with a flag for first \x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[48;2;39;40;34m \x1b[0m\x1b[38;2;230;219;116;48;2;39;40;34mand last value."""\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 3 \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 4 \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mtry\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 5 \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mnext\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 6 \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mexcept\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34mStopIteration\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 7 \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mreturn\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 8 \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mTrue\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 9 \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mfor\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34min\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m10 \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mFalse\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n╰────────────────────────────────────────────────────────────────╯\n' assert rendered_syntax == expected def test_python_render_simple(): syntax = Syntax( CODE, - lexer_name="python", + lexer="python", line_numbers=False, - theme="foo", + theme="monokai", + code_width=60, + word_wrap=False, + ) + rendered_syntax = render(syntax) + print(repr(rendered_syntax)) + expected = '\x1b[38;2;102;217;239;48;2;39;40;34mdef\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34mloop_first_last\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mIterable\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m[\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mT\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m]\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m-\x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m>\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mIterable\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m[\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mTuple\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m[\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mb\x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;230;219;116;48;2;39;40;34m"""Iterate and generate a tuple with a flag for first an\x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mtry\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mnext\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mexcept\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34mStopIteration\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mreturn\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mTrue\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mfor\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34min\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mFalse\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mFalse\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mTrue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n' + assert rendered_syntax == expected + + +def test_python_render_simple_passing_lexer_instance(): + syntax = Syntax( + CODE, + lexer=PythonLexer(), + line_numbers=False, + theme="monokai", code_width=60, word_wrap=False, ) rendered_syntax = render(syntax) print(repr(rendered_syntax)) - expected = '\x1b[1;38;2;0;128;0;48;2;248;248;248mdef\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;255;48;2;248;248;248mloop_first_last\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m(\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalues\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mIterable\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m[\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mT\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m]\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m)\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m-\x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m>\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mIterable\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m[\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mTuple\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m[\x1b[0m\x1b[38;2;0;128;0;48;2;248;248;248mb\x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[3;38;2;186;33;33;48;2;248;248;248m"""Iterate and generate a tuple with a flag for first an\x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;128;0;48;2;248;248;248miter\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m(\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalues\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m)\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mtry\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;128;0;48;2;248;248;248mnext\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m(\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m)\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mexcept\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;210;65;58;48;2;248;248;248mStopIteration\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mreturn\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mTrue\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mfor\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalue\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;170;34;255;48;2;248;248;248min\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248myield\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mFalse\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mFalse\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalue\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248myield\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mTrue\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n' + expected = '\x1b[38;2;102;217;239;48;2;39;40;34mdef\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34mloop_first_last\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mIterable\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m[\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mT\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m]\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m-\x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m>\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mIterable\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m[\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mTuple\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m[\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mb\x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;230;219;116;48;2;39;40;34m"""Iterate and generate a tuple with a flag for first an\x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mtry\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mnext\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mexcept\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34mStopIteration\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mreturn\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mTrue\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mfor\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34min\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mFalse\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mFalse\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mTrue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\n' assert rendered_syntax == expected def test_python_render_simple_indent_guides(): syntax = Syntax( CODE, - lexer_name="python", + lexer="python", line_numbers=False, theme="ansi_light", code_width=60, @@ -93,7 +110,7 @@ def test_python_render_simple_indent_guides(): def test_python_render_line_range_indent_guides(): syntax = Syntax( CODE, - lexer_name="python", + lexer="python", line_numbers=False, theme="ansi_light", code_width=60, @@ -111,10 +128,10 @@ def test_python_render_indent_guides(): syntax = Panel.fit( Syntax( CODE, - lexer_name="python", + lexer="python", line_numbers=True, line_range=(2, 10), - theme="foo", + theme="monokai", code_width=60, word_wrap=True, indent_guides=True, @@ -123,7 +140,7 @@ def test_python_render_indent_guides(): ) rendered_syntax = render(syntax) print(repr(rendered_syntax)) - expected = '╭────────────────────────────────────────────────────────────────╮\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 2 \x1b[0m\x1b[2;3;38;2;64;128;128;48;2;248;248;248m│ \x1b[0m\x1b[3;38;2;186;33;33;48;2;248;248;248m"""Iterate and generate a tuple with a flag for first \x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[48;2;248;248;248m \x1b[0m\x1b[3;38;2;186;33;33;48;2;248;248;248mand last value."""\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 3 \x1b[0m\x1b[2;3;38;2;64;128;128;48;2;248;248;248m│ \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;128;0;48;2;248;248;248miter\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m(\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalues\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m)\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 4 \x1b[0m\x1b[2;3;38;2;64;128;128;48;2;248;248;248m│ \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mtry\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 5 \x1b[0m\x1b[2;3;38;2;64;128;128;48;2;248;248;248m│ │ \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;128;0;48;2;248;248;248mnext\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m(\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m)\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 6 \x1b[0m\x1b[2;3;38;2;64;128;128;48;2;248;248;248m│ \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mexcept\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;210;65;58;48;2;248;248;248mStopIteration\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 7 \x1b[0m\x1b[2;3;38;2;64;128;128;48;2;248;248;248m│ │ \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mreturn\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 8 \x1b[0m\x1b[2;3;38;2;64;128;128;48;2;248;248;248m│ \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mTrue\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m 9 \x1b[0m\x1b[2;3;38;2;64;128;128;48;2;248;248;248m│ \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mfor\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalue\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;170;34;255;48;2;248;248;248min\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n│\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m10 \x1b[0m\x1b[2;3;38;2;64;128;128;48;2;248;248;248m│ │ \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248myield\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mFalse\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[48;2;248;248;248m \x1b[0m│\n╰────────────────────────────────────────────────────────────────╯\n' + expected = '╭────────────────────────────────────────────────────────────────╮\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 2 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;230;219;116;48;2;39;40;34m"""Iterate and generate a tuple with a flag for first \x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[48;2;39;40;34m \x1b[0m\x1b[38;2;230;219;116;48;2;39;40;34mand last value."""\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 3 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 4 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mtry\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 5 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mnext\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 6 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mexcept\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34mStopIteration\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 7 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mreturn\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 8 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mTrue\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 9 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mfor\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34min\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n│\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m10 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfirst\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mFalse\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m│\n╰────────────────────────────────────────────────────────────────╯\n' assert rendered_syntax == expected @@ -144,7 +161,7 @@ def test_get_line_color_none(): style._background_style = Style(bgcolor=None) syntax = Syntax( CODE, - lexer_name="python", + lexer="python", line_numbers=True, line_range=(2, 10), theme=style, @@ -158,7 +175,7 @@ def test_get_line_color_none(): def test_highlight_background_color(): syntax = Syntax( CODE, - lexer_name="python", + lexer="python", line_numbers=True, line_range=(2, 10), theme="foo", @@ -189,7 +206,7 @@ def test_get_style_for_token(): style._style_cache = style_dict syntax = Syntax( CODE, - lexer_name="python", + lexer="python", line_numbers=True, line_range=(2, 10), theme=style, @@ -203,7 +220,7 @@ def test_get_style_for_token(): def test_option_no_wrap(): syntax = Syntax( CODE, - lexer_name="python", + lexer="python", line_numbers=True, line_range=(2, 10), code_width=60, @@ -224,35 +241,73 @@ def test_ansi_theme(): assert theme.get_background_style() == Style() -@pytest.mark.skipif(sys.platform == "win32", reason="permissions error on Windows") -def test_from_file(): +skip_windows_permission_error = pytest.mark.skipif( + sys.platform == "win32", reason="permissions error on Windows" +) + + +@skip_windows_permission_error +def test_from_path(): fh, path = tempfile.mkstemp("example.py") try: os.write(fh, b"import this\n") syntax = Syntax.from_path(path) - assert syntax.lexer_name == "Python" + assert syntax.lexer + assert syntax.lexer.name == "Python" assert syntax.code == "import this\n" finally: os.remove(path) -@pytest.mark.skipif(sys.platform == "win32", reason="permissions error on Windows") -def test_from_file_unknown_lexer(): +@skip_windows_permission_error +def test_from_path_unknown_lexer(): fh, path = tempfile.mkstemp("example.nosuchtype") try: os.write(fh, b"import this\n") syntax = Syntax.from_path(path) - assert syntax.lexer_name == "default" + assert syntax.lexer is None assert syntax.code == "import this\n" finally: os.remove(path) +@skip_windows_permission_error +def test_from_path_lexer_override(): + fh, path = tempfile.mkstemp("example.nosuchtype") + try: + os.write(fh, b"import this\n") + syntax = Syntax.from_path(path, lexer="rust") + assert syntax.lexer.name is "Rust" + assert syntax.code == "import this\n" + finally: + os.remove(path) + + +@skip_windows_permission_error +def test_from_path_lexer_override_invalid_lexer(): + fh, path = tempfile.mkstemp("example.nosuchtype") + try: + os.write(fh, b"import this\n") + syntax = Syntax.from_path(path, lexer="blah") + assert syntax.lexer is None + assert syntax.code == "import this\n" + finally: + os.remove(path) + + +def test_syntax_guess_lexer(): + assert Syntax.guess_lexer("banana.py") == "python" + assert Syntax.guess_lexer("banana.py", "import this") == "python" + assert Syntax.guess_lexer("banana.html", "hello") == "html" + assert Syntax.guess_lexer("banana.html", "<%= @foo %>") == "rhtml" + assert Syntax.guess_lexer("banana.html", "{{something|filter:3}}") == "html+django" + + if __name__ == "__main__": syntax = Panel.fit( Syntax( CODE, - lexer_name="python", + lexer="python", line_numbers=True, line_range=(2, 10), theme="foo", diff --git a/tests/test_table.py b/tests/test_table.py index 29826e1182..6fc863f20c 100644 --- a/tests/test_table.py +++ b/tests/test_table.py @@ -4,11 +4,12 @@ import pytest -from rich import errors +from rich import box, errors +from rich.align import VerticalAlignMethod from rich.console import Console from rich.measure import Measurement from rich.style import Style -from rich.table import Table, Column +from rich.table import Column, Table from rich.text import Text @@ -158,6 +159,29 @@ def test_get_row_style(): assert table.get_row_style(console, 1) == Style.parse("on red") +def test_vertical_align_top(): + console = Console(_environ={}) + + def make_table(vertical_align): + table = Table(show_header=False, box=box.SQUARE) + table.add_column(vertical=vertical_align) + table.add_row("foo", "\n".join(["bar"] * 5)) + + return table + + with console.capture() as capture: + console.print(make_table("top")) + console.print() + console.print(make_table("middle")) + console.print() + console.print(make_table("bottom")) + console.print() + result = capture.get() + print(repr(result)) + expected = "┌─────┬─────┐\n│ foo │ bar │\n│ │ bar │\n│ │ bar │\n│ │ bar │\n│ │ bar │\n└─────┴─────┘\n\n┌─────┬─────┐\n│ │ bar │\n│ │ bar │\n│ foo │ bar │\n│ │ bar │\n│ │ bar │\n└─────┴─────┘\n\n┌─────┬─────┐\n│ │ bar │\n│ │ bar │\n│ │ bar │\n│ │ bar │\n│ foo │ bar │\n└─────┴─────┘\n\n" + assert result == expected + + if __name__ == "__main__": render = render_tables() print(render) diff --git a/tests/test_text.py b/tests/test_text.py index 3727d1602c..d1d721bd4e 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -95,6 +95,16 @@ def test_from_markup(): assert text._spans == [Span(7, 13, "bold")] +def test_from_ansi(): + text = Text.from_ansi("Hello, \033[1mWorld!\033[0m") + assert str(text) == "Hello, World!" + assert text._spans == [Span(7, 13, Style(bold=True))] + + text = Text.from_ansi("Hello, \033[1m\nWorld!\033[0m") + assert str(text) == "Hello, \nWorld!" + assert text._spans == [Span(8, 14, Style(bold=True))] + + def test_copy(): test = Text() test.append("Hello", "bold") @@ -705,3 +715,19 @@ def test_on(): assert text.get_style_at_offset(console, 0).meta == expected assert text.get_style_at_offset(console, 1).meta == expected assert text.get_style_at_offset(console, 2).meta == expected + + +def test_markup_property(): + assert Text("").markup == "" + assert Text("foo").markup == "foo" + assert Text("foo", style="bold").markup == "[bold]foo[/bold]" + assert Text.from_markup("foo [red]bar[/red]").markup == "foo [red]bar[/red]" + assert ( + Text.from_markup("foo [red]bar[/red]", style="bold").markup + == "[bold]foo [red]bar[/red][/bold]" + ) + assert ( + Text.from_markup("[bold]foo [italic]bar[/bold] baz[/italic]").markup + == "[bold]foo [italic]bar[/bold] baz[/italic]" + ) + assert Text("[bold]foo").markup == "\\[bold]foo" diff --git a/tests/test_traceback.py b/tests/test_traceback.py index 3b3e8de65d..31658c5a43 100644 --- a/tests/test_traceback.py +++ b/tests/test_traceback.py @@ -4,7 +4,8 @@ import pytest from rich.console import Console -from rich.traceback import install, Traceback +from rich.theme import Theme +from rich.traceback import Traceback, install # from .render import render @@ -109,8 +110,8 @@ def test_syntax_error(): console = Console(width=100, file=io.StringIO()) try: # raises SyntaxError: unexpected EOF while parsing - eval("(2 + 2") - except Exception: + eval("(2+2") + except SyntaxError: console.print_exception() exception_text = console.file.getvalue() assert "SyntaxError" in exception_text @@ -190,6 +191,30 @@ def test_filename_not_a_file(): assert "string" in exception_text +@pytest.mark.skipif(sys.platform == "win32", reason="renders different on windows") +def test_traceback_console_theme_applies(): + """ + Ensure that themes supplied via Console init work on Tracebacks. + Regression test for https://github.com/Textualize/rich/issues/1786 + """ + r, g, b = 123, 234, 123 + console = Console( + force_terminal=True, + _environ={"COLORTERM": "truecolor"}, + theme=Theme({"traceback.title": f"rgb({r},{g},{b})"}), + ) + + console.begin_capture() + try: + 1 / 0 + except Exception: + console.print_exception() + + result = console.end_capture() + + assert f"\\x1b[38;2;{r};{g};{b}mTraceback \\x1b[0m" in repr(result) + + def test_broken_str(): class BrokenStr(Exception): def __str__(self): diff --git a/tests/test_tree.py b/tests/test_tree.py index babcc3cf27..f5510aa536 100644 --- a/tests/test_tree.py +++ b/tests/test_tree.py @@ -67,7 +67,9 @@ def test_render_tree_non_win32(): baz_tree.add("2") tree.add("egg") - console = Console(width=20, force_terminal=True, color_system="standard") + console = Console( + width=20, force_terminal=True, color_system="standard", _environ={} + ) console.begin_capture() console.print(tree) result = console.end_capture() @@ -94,6 +96,44 @@ def test_render_tree_win32(): assert result == expected +@pytest.mark.skipif(sys.platform == "win32", reason="different on Windows") +def test_render_tree_hide_root_non_win32(): + tree = Tree("foo", hide_root=True) + tree.add("bar", style="italic") + baz_tree = tree.add("baz", guide_style="bold red", style="on blue") + baz_tree.add("1") + baz_tree.add("2") + tree.add("egg") + + console = Console( + width=20, force_terminal=True, color_system="standard", _environ={} + ) + console.begin_capture() + console.print(tree) + result = console.end_capture() + print(repr(result)) + expected = "\x1b[3mbar\x1b[0m\x1b[3m \x1b[0m\n\x1b[44mbaz\x1b[0m\x1b[44m \x1b[0m\n\x1b[31;44m┣━━ \x1b[0m\x1b[44m1\x1b[0m\x1b[44m \x1b[0m\n\x1b[31;44m┗━━ \x1b[0m\x1b[44m2\x1b[0m\x1b[44m \x1b[0m\negg \n" + assert result == expected + + +@pytest.mark.skipif(sys.platform != "win32", reason="Windows specific") +def test_render_tree_hide_root_win32(): + tree = Tree("foo", hide_root=True) + tree.add("bar", style="italic") + baz_tree = tree.add("baz", guide_style="bold red", style="on blue") + baz_tree.add("1") + baz_tree.add("2") + tree.add("egg") + + console = Console(width=20, force_terminal=True, color_system="standard") + console.begin_capture() + console.print(tree) + result = console.end_capture() + print(repr(result)) + expected = "\x1b[3mbar\x1b[0m\x1b[3m \x1b[0m\n\x1b[44mbaz\x1b[0m\x1b[44m \x1b[0m\n\x1b[31;44m├── \x1b[0m\x1b[44m1\x1b[0m\x1b[44m \x1b[0m\n\x1b[31;44m└── \x1b[0m\x1b[44m2\x1b[0m\x1b[44m \x1b[0m\negg \n" + assert result == expected + + def test_tree_measure(): tree = Tree("foo") tree.add("bar") diff --git a/tools/stress_test_pretty.py b/tools/stress_test_pretty.py index 4a945d1114..334f9cf544 100644 --- a/tools/stress_test_pretty.py +++ b/tools/stress_test_pretty.py @@ -1,6 +1,7 @@ from rich.console import Console from rich.panel import Panel from rich.pretty import Pretty +from rich._timer import timer DATA = { "foo": [1, 2, 3, (), {}, (1, 2, 3), {4, 5, 6, (7, 8, 9)}, "Hello, World"], @@ -15,5 +16,6 @@ }, } console = Console() -for w in range(130): - console.print(Panel(Pretty(DATA, indent_guides=True), width=w)) +with timer("Stress test"): + for w in range(130): + console.print(Panel(Pretty(DATA, indent_guides=True), width=w))