├── .coveragerc
├── .faq
├── FAQ.md
└── suggest.md
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ ├── codeql.yml
│ ├── codespell.yml
│ ├── comment.yml
│ ├── newissue.yml
│ ├── pythonpackage.yml
│ └── readmechanged.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CONTRIBUTORS.md
├── FAQ.md
├── LICENSE
├── Makefile
├── README.cn.md
├── README.de-ch.md
├── README.de.md
├── README.es.md
├── README.fa.md
├── README.fr.md
├── README.hi.md
├── README.id.md
├── README.it.md
├── README.ja.md
├── README.kr.md
├── README.md
├── README.pl.md
├── README.pt-br.md
├── README.ru.md
├── README.sv.md
├── README.tr.md
├── README.zh-tw.md
├── SECURITY.md
├── assets
├── logo.ai
├── logo.svg
└── logo.txt
├── asv.conf.json
├── asvhashfile
├── benchmarks
├── README.md
├── __init__.py
├── benchmarks.py
├── results
│ ├── benchmarks.json
│ └── darrenburns-2022-mbp
│ │ ├── 008854c4-virtualenv-py3.10.json
│ │ ├── 03392a1b-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 038e22eb-virtualenv-py3.10.json
│ │ ├── 03a52134-virtualenv-py3.10.json
│ │ ├── 06922006-virtualenv-py3.10.json
│ │ ├── 06aa1271-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 07d51ffc-virtualenv-py3.10.json
│ │ ├── 0a3fcb9c-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 0ac4e308-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 0d2aeb75-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 0d69004c-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 0db0cbd0-virtualenv-py3.10.json
│ │ ├── 0e8df8cd-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 0fd6bc56-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 11c00224-virtualenv-py3.10.json
│ │ ├── 11c305e1-virtualenv-py3.10.json
│ │ ├── 1442dd77-virtualenv-py3.10.json
│ │ ├── 15623c5a-virtualenv-py3.10.json
│ │ ├── 177958c5-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 189a2a3f-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 19e26c94-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 1cdcd1ae-virtualenv-py3.10.json
│ │ ├── 1daa1771-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 1f3f7f1e-virtualenv-py3.10.json
│ │ ├── 1ffbd443-virtualenv-py3.10.json
│ │ ├── 20024635-virtualenv-py3.10.json
│ │ ├── 21432b4c-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 2356d7c0-virtualenv-py3.10.json
│ │ ├── 23aa7177-virtualenv-py3.10.json
│ │ ├── 24743154-virtualenv-py3.10.json
│ │ ├── 25a1bf06-virtualenv-py3.10.json
│ │ ├── 26fe4667-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 27ab1732-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 2aea8526-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 2ba277ac-virtualenv-py3.10.json
│ │ ├── 2c93dce9-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 2d3152a2-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 2d3ec69f-virtualenv-py3.10.json
│ │ ├── 2ea7e586-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 2ea7e586-virtualenv-py3.10.json
│ │ ├── 30498f59-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 3473658d-virtualenv-py3.10.json
│ │ ├── 36efcb5a-virtualenv-py3.10.json
│ │ ├── 3827b4ae-virtualenv-py3.10.json
│ │ ├── 3be88c08-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 3db6396a-virtualenv-py3.10.json
│ │ ├── 3f7d3e4e-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 3f7d3e4e-virtualenv-py3.10.json
│ │ ├── 4020d5a9-virtualenv-py3.10.json
│ │ ├── 41279bca-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 416033ff-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 43a26c0a-virtualenv-py3.10.json
│ │ ├── 43d4c4e5-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 43d4c4e5-virtualenv-py3.10.json
│ │ ├── 44f54dd8-virtualenv-py3.10.json
│ │ ├── 464e4e33-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 489fafc6-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 48da2791-virtualenv-py3.10.json
│ │ ├── 4b123ddf-virtualenv-py3.10.json
│ │ ├── 4b3b6531-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 4bf3f19c-virtualenv-py3.10.json
│ │ ├── 4d6a6d88-virtualenv-py3.10.json
│ │ ├── 4dc1d4cb-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 4f8908a6-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 52d159aa-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 52d159aa-virtualenv-py3.10.json
│ │ ├── 53cda574-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 53d9eeaf-virtualenv-py3.10.json
│ │ ├── 550d3911-virtualenv-py3.10.json
│ │ ├── 55e11902-virtualenv-py3.10.json
│ │ ├── 573125e9-virtualenv-py3.10.json
│ │ ├── 579a29c8-virtualenv-py3.10.json
│ │ ├── 588f0331-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 58bfa48f-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 5f021978-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 5f021978-virtualenv-py3.10.json
│ │ ├── 5f03e3ba-virtualenv-py3.10.json
│ │ ├── 5f55063b-virtualenv-py3.10.json
│ │ ├── 5f82274a-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 5fafb92f-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 60dadaf2-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 64471afc-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 646d933d-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 64755d41-virtualenv-py3.10.json
│ │ ├── 656b7a18-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 666d0cf2-virtualenv-py3.10.json
│ │ ├── 690507d4-virtualenv-py3.10.json
│ │ ├── 6d7ba589-virtualenv-py3.10.json
│ │ ├── 71135d19-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 7441bf27-virtualenv-py3.10.json
│ │ ├── 76620730-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 79ea1c1d-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 7bad81d5-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 7d00fa83-virtualenv-py3.10.json
│ │ ├── 7d02d29b-virtualenv-py3.10.json
│ │ ├── 7e4a2db4-virtualenv-py3.10.json
│ │ ├── 7edd619f-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 7ef7ffee-virtualenv-py3.10.json
│ │ ├── 83756d62-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 837b6d7e-virtualenv-py3.10.json
│ │ ├── 877c53d9-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 88b07b3e-virtualenv-py3.10.json
│ │ ├── 8a7f5d82-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 8a7f5d82-virtualenv-py3.10.json
│ │ ├── 8b185610-virtualenv-py3.10.json
│ │ ├── 8b47f338-virtualenv-py3.10.json
│ │ ├── 8c3e6be4-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 8e649fea-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 911d305f-virtualenv-py3.10.json
│ │ ├── 932e26b6-virtualenv-py3.10.json
│ │ ├── 949e1f72-virtualenv-py3.10.json
│ │ ├── 95d8bf98-virtualenv-py3.10.json
│ │ ├── 96ea5fed-virtualenv-py3.10.json
│ │ ├── 972dedff-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 972dedff-virtualenv-py3.10.json
│ │ ├── 99831099-virtualenv-py3.10.json
│ │ ├── 9a4fbf83-virtualenv-py3.10.json
│ │ ├── 9abc0292-virtualenv-py3.10.json
│ │ ├── 9bfb6190-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── 9f2a426e-virtualenv-py3.10.json
│ │ ├── a27a3ee2-virtualenv-py3.10.json
│ │ ├── a2f6688e-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── a6d1d784-virtualenv-py3.10.json
│ │ ├── a6ea9890-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── a81230bc-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── a81230bc-virtualenv-py3.10.json
│ │ ├── a8d2bb20-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── aa4546ac-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── aa7926c1-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── aaea99f7-virtualenv-py3.10.json
│ │ ├── ac1a33da-virtualenv-py3.10.json
│ │ ├── aca0b60b-virtualenv-py3.10.json
│ │ ├── ad6e3dea-virtualenv-py3.10.json
│ │ ├── ae5865eb-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── b15bc18c-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── b391635e-virtualenv-py3.10.json
│ │ ├── b9e0014a-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── b9e0014a-virtualenv-py3.10.json
│ │ ├── ba5d0c2c-virtualenv-py3.10.json
│ │ ├── bd34e0a1-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── bd34e0a1-virtualenv-py3.10.json
│ │ ├── bf728dbc-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── c24ab497-virtualenv-py3.10.json
│ │ ├── c3d0e358-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── c3d0e358-virtualenv-py3.10.json
│ │ ├── c3ee3b05-virtualenv-py3.10.json
│ │ ├── c57e1f50-virtualenv-py3.10.json
│ │ ├── c9afafdd-virtualenv-py3.10.json
│ │ ├── cefafdc1-virtualenv-py3.10.json
│ │ ├── cf606f0a-virtualenv-py3.10.json
│ │ ├── d06540a2-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── d1ea01d0-virtualenv-py3.10.json
│ │ ├── d6e6a762-virtualenv-py3.10.json
│ │ ├── d9d59c6e-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── d9d59c6e-virtualenv-py3.10.json
│ │ ├── dc3f0623-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── e0a1fd30-virtualenv-py3.10.json
│ │ ├── e21ac11a-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── e338ab14-virtualenv-py3.10.json
│ │ ├── e34eadb3-virtualenv-py3.10.json
│ │ ├── e5246436-virtualenv-py3.10.json
│ │ ├── e7849495-virtualenv-py3.10.json
│ │ ├── e78acae6-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── e7de32a0-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── e9e72000-virtualenv-py3.10.json
│ │ ├── ea049ffc-virtualenv-py3.10.json
│ │ ├── ea2ed337-virtualenv-py3.10.json
│ │ ├── eab3fe8e-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── ecf3d7f1-virtualenv-py3.10.json
│ │ ├── edcb6f9e-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── ef80460f-virtualenv-py3.10.json
│ │ ├── f2845e12-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── f2af8c9d-virtualenv-py3.10.json
│ │ ├── f5ed5bde-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── f82a4ccf-virtualenv-py3.10-setuptools59.2.0.json
│ │ ├── f84d5dee-virtualenv-py3.10.json
│ │ └── machine.json
└── snippets.py
├── docs
├── Makefile
├── images
│ ├── box.svg
│ └── svg_export.svg
├── make.bat
├── requirements.txt
└── source
│ ├── appendix.rst
│ ├── appendix
│ ├── box.rst
│ └── colors.rst
│ ├── columns.rst
│ ├── conf.py
│ ├── console.rst
│ ├── group.rst
│ ├── highlighting.rst
│ ├── index.rst
│ ├── introduction.rst
│ ├── layout.rst
│ ├── live.rst
│ ├── logging.rst
│ ├── markdown.rst
│ ├── markup.rst
│ ├── padding.rst
│ ├── panel.rst
│ ├── pretty.rst
│ ├── progress.rst
│ ├── prompt.rst
│ ├── protocol.rst
│ ├── reference.rst
│ ├── reference
│ ├── abc.rst
│ ├── align.rst
│ ├── bar.rst
│ ├── color.rst
│ ├── columns.rst
│ ├── console.rst
│ ├── emoji.rst
│ ├── highlighter.rst
│ ├── init.rst
│ ├── json.rst
│ ├── layout.rst
│ ├── live.rst
│ ├── logging.rst
│ ├── markdown.rst
│ ├── markup.rst
│ ├── measure.rst
│ ├── padding.rst
│ ├── panel.rst
│ ├── pretty.rst
│ ├── progress.rst
│ ├── progress_bar.rst
│ ├── prompt.rst
│ ├── protocol.rst
│ ├── rule.rst
│ ├── segment.rst
│ ├── spinner.rst
│ ├── status.rst
│ ├── style.rst
│ ├── styled.rst
│ ├── syntax.rst
│ ├── table.rst
│ ├── text.rst
│ ├── theme.rst
│ ├── traceback.rst
│ └── tree.rst
│ ├── style.rst
│ ├── syntax.rst
│ ├── tables.rst
│ ├── text.rst
│ ├── traceback.rst
│ └── tree.rst
├── examples
├── README.md
├── attrs.py
├── bars.py
├── columns.py
├── cp_progress.py
├── downloader.py
├── dynamic_progress.py
├── exception.py
├── export.py
├── file_progress.py
├── fullscreen.py
├── group.py
├── group2.py
├── highlighter.py
├── jobs.py
├── justify.py
├── justify2.py
├── layout.py
├── link.py
├── listdir.py
├── live_progress.py
├── log.py
├── overflow.py
├── padding.py
├── print_calendar.py
├── rainbow.py
├── recursive_error.py
├── repr.py
├── save_table_svg.py
├── screen.py
├── spinners.py
├── status.py
├── suppress.py
├── table.py
├── table_movie.py
├── top_lite_simulator.py
└── tree.py
├── faq.yml
├── imgs
├── columns.png
├── downloader.gif
├── features.png
├── hello_world.png
├── inspect.png
├── log.png
├── logging.png
├── logo.svg
├── markdown.png
├── print.png
├── progress.gif
├── progress.png
├── repl.png
├── spinners.gif
├── status.gif
├── syntax.png
├── table.png
├── table2.png
├── table_movie.gif
├── traceback.png
├── tree.png
└── where_there_is_a_will.png
├── make.bat
├── poetry.lock
├── pyproject.toml
├── questions
├── README.md
├── ansi_escapes.question.md
├── emoji_broken.question.md
├── highlighting_unexpected.question.md
├── log_renderables.question.md
├── logging_color.question.md
├── rich_spinner.question.md
├── square_brackets.question.md
└── tracebacks_installed.question.md
├── rich
├── __init__.py
├── __main__.py
├── _cell_widths.py
├── _emoji_codes.py
├── _emoji_replace.py
├── _export_format.py
├── _extension.py
├── _fileno.py
├── _inspect.py
├── _log_render.py
├── _loop.py
├── _null_file.py
├── _palettes.py
├── _pick.py
├── _ratio.py
├── _spinners.py
├── _stack.py
├── _timer.py
├── _win32_console.py
├── _windows.py
├── _windows_renderer.py
├── _wrap.py
├── abc.py
├── align.py
├── ansi.py
├── bar.py
├── box.py
├── cells.py
├── color.py
├── color_triplet.py
├── columns.py
├── console.py
├── constrain.py
├── containers.py
├── control.py
├── default_styles.py
├── diagnose.py
├── emoji.py
├── errors.py
├── file_proxy.py
├── filesize.py
├── highlighter.py
├── json.py
├── jupyter.py
├── layout.py
├── live.py
├── live_render.py
├── logging.py
├── markdown.py
├── markup.py
├── measure.py
├── padding.py
├── pager.py
├── palette.py
├── panel.py
├── pretty.py
├── progress.py
├── progress_bar.py
├── prompt.py
├── protocol.py
├── py.typed
├── region.py
├── repr.py
├── rule.py
├── scope.py
├── screen.py
├── segment.py
├── spinner.py
├── status.py
├── style.py
├── styled.py
├── syntax.py
├── table.py
├── terminal_theme.py
├── text.py
├── theme.py
├── themes.py
├── traceback.py
└── tree.py
├── setup.py
├── tests
├── __init__.py
├── _card_render.py
├── conftest.py
├── pytest.ini
├── render.py
├── test_align.py
├── test_ansi.py
├── test_bar.py
├── test_block_bar.py
├── test_box.py
├── test_card.py
├── test_cells.py
├── test_color.py
├── test_color_triplet.py
├── test_columns.py
├── test_columns_align.py
├── test_console.py
├── test_constrain.py
├── test_containers.py
├── test_control.py
├── test_emoji.py
├── test_file_proxy.py
├── test_filesize.py
├── test_getfileno.py
├── test_highlighter.py
├── test_inspect.py
├── test_json.py
├── test_jupyter.py
├── test_layout.py
├── test_live.py
├── test_live_render.py
├── test_log.py
├── test_logging.py
├── test_markdown.py
├── test_markdown_no_hyperlinks.py
├── test_markup.py
├── test_measure.py
├── test_null_file.py
├── test_padding.py
├── test_palette.py
├── test_panel.py
├── test_pick.py
├── test_pretty.py
├── test_progress.py
├── test_prompt.py
├── test_protocol.py
├── test_ratio.py
├── test_repr.py
├── test_rich_print.py
├── test_rule.py
├── test_rule_in_table.py
├── test_screen.py
├── test_segment.py
├── test_spinner.py
├── test_stack.py
├── test_status.py
├── test_style.py
├── test_styled.py
├── test_syntax.py
├── test_table.py
├── test_text.py
├── test_theme.py
├── test_tools.py
├── test_traceback.py
├── test_tree.py
├── test_win32_console.py
└── test_windows_renderer.py
├── tools
├── README.md
├── cats.json
├── make_emoji.py
├── make_terminal_widths.py
├── movies.md
├── profile_divide.py
├── profile_pretty.py
└── stress_test_pretty.py
└── tox.ini
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | omit = rich/jupyter.py
3 | rich/_windows.py
4 | rich/_timer.py
5 | rich/diagnose.py
6 |
7 | [report]
8 | exclude_lines =
9 | pragma: no cover
10 | if TYPE_CHECKING:
11 | if __name__ == "__main__":
12 | @overload
13 |
--------------------------------------------------------------------------------
/.faq/FAQ.md:
--------------------------------------------------------------------------------
1 |
2 | # Frequently Asked Questions
3 |
4 | {%- for question in questions %}
5 | - [{{ question.title }}](#{{ question.slug }})
6 | {%- endfor %}
7 |
8 |
9 | {%- for question in questions %}
10 |
11 |
12 | ## {{ question.title }}
13 |
14 | {{ question.body }}
15 |
16 | {%- endfor %}
17 |
18 |
19 |
20 | Generated by [FAQtory](https://github.com/willmcgugan/faqtory)
21 |
--------------------------------------------------------------------------------
/.faq/suggest.md:
--------------------------------------------------------------------------------
1 | {%- if questions -%}
2 | {% if questions|length == 1 %}
3 | We found the following entry in the [FAQ]({{ faq_url }}) which you may find helpful:
4 | {%- else %}
5 | We found the following entries in the [FAQ]({{ faq_url }}) which you may find helpful:
6 | {%- endif %}
7 |
8 | {% for question in questions %}
9 | - [{{ question.title }}]({{ faq_url }}#{{ question.slug }})
10 | {%- endfor %}
11 |
12 | Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.
13 |
14 | {%- else -%}
15 | Thank you for your issue. Give us a little time to review it.
16 |
17 | PS. You might want to check the [FAQ]({{ faq_url }}) if you haven't done so already.
18 | {%- endif %}
19 |
20 | This is an automated reply, generated by [FAQtory](https://github.com/willmcgugan/faqtory)
21 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: Needs triage
6 | assignees: ""
7 | ---
8 |
9 | - [ ] I've checked [docs](https://rich.readthedocs.io/en/latest/introduction.html) and [closed issues](https://github.com/Textualize/rich/issues?q=is%3Aissue+is%3Aclosed) for possible solutions.
10 | - [ ] I can't find my issue in the [FAQ](https://github.com/Textualize/rich/blob/master/FAQ.md).
11 |
12 | **Describe the bug**
13 |
14 | Edit this with a clear and concise description of what the bug.
15 |
16 | Provide a minimal code example that demonstrates the issue if you can. If the issue is visual in nature, consider posting a screenshot.
17 |
18 | **Platform**
19 |
20 | Click to expand
21 |
22 | What platform (Win/Linux/Mac) are you running on? What terminal software are you using?
23 |
24 | I may ask you to copy and paste the output of the following commands. It may save some time if you do it now.
25 |
26 | If you're using Rich in a terminal:
27 |
28 | ```
29 | python -m rich.diagnose
30 | pip freeze | grep rich
31 | ```
32 |
33 | If you're using Rich in a Jupyter Notebook, run the following snippet in a cell
34 | and paste the output in your bug report.
35 |
36 | ```python
37 | from rich.diagnose import report
38 | report()
39 | ```
40 |
41 |
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: "[REQUEST]"
5 | labels: Needs triage
6 | assignees: ''
7 |
8 | ---
9 |
10 | NOTE: If a feature could be implemented in a third-party library, then it is unlikely to be accepted as part of the core library.
11 |
12 | Consider posting in https://github.com/textualize/rich/discussions for feedback before raising a feature request.
13 |
14 | Have you checked the issues for a similar suggestions?
15 |
16 | **How would you improve Rich?**
17 |
18 | Give as much detail as you can. Example code of how you would like it to work would help.
19 |
20 | **What problem does it solve for you?**
21 |
22 | What problem do you have that this feature would solve? I may be able to suggest an existing way of solving it.
23 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "pip" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "daily"
12 | - package-ecosystem: "github-actions"
13 | directory: "/"
14 | schedule:
15 | interval: "daily"
16 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Type of changes
2 |
3 | - [ ] Bug fix
4 | - [ ] New feature
5 | - [ ] Documentation / docstrings
6 | - [ ] Tests
7 | - [ ] Other
8 |
9 | ## Checklist
10 |
11 | - [ ] I've run the latest [black](https://github.com/psf/black) with default args on new code.
12 | - [ ] I've updated CHANGELOG.md and CONTRIBUTORS.md where appropriate.
13 | - [ ] I've added tests for new code.
14 | - [ ] I accept that @willmcgugan may be pedantic in the code review.
15 |
16 | ## Description
17 |
18 | Please describe your changes here. If this fixes a bug, please link to the issue, if possible.
19 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ 'master' ]
6 | pull_request:
7 | # The branches below must be a subset of the branches above
8 | branches: [ 'master' ]
9 | schedule:
10 | - cron: '38 19 * * 5'
11 |
12 | jobs:
13 | analyze:
14 | name: Analyze
15 | runs-on: ubuntu-latest
16 | permissions:
17 | actions: read
18 | contents: read
19 | security-events: write
20 |
21 | strategy:
22 | fail-fast: false
23 | matrix:
24 | language: [ 'python' ]
25 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
26 | # Use only 'java' to analyze code written in Java, Kotlin or both
27 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
28 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
29 |
30 | steps:
31 | - name: Checkout repository
32 | uses: actions/checkout@v4
33 |
34 | # Initializes the CodeQL tools for scanning.
35 | - name: Initialize CodeQL
36 | uses: github/codeql-action/init@v3
37 | with:
38 | languages: ${{ matrix.language }}
39 | # If you wish to specify custom queries, you can do so here or in a config file.
40 | # By default, queries listed here will override any specified in a config file.
41 | # Prefix the list here with "+" to use these queries and those in the config file.
42 |
43 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
44 | # queries: security-extended,security-and-quality
45 |
46 |
47 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
48 | # If this step fails, then you should remove it and run the build manually (see below)
49 | - name: Autobuild
50 | uses: github/codeql-action/autobuild@v3
51 |
52 | # ℹ️ Command-line programs to run using the OS shell.
53 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
54 |
55 | # If the Autobuild fails above, remove it and uncomment the following three lines.
56 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
57 |
58 | # - run: |
59 | # echo "Run, Build Application using script"
60 | # ./location_of_script_within_repo/buildscript.sh
61 |
62 | - name: Perform CodeQL Analysis
63 | uses: github/codeql-action/analyze@v3
64 | with:
65 | category: "/language:${{matrix.language}}"
66 |
--------------------------------------------------------------------------------
/.github/workflows/codespell.yml:
--------------------------------------------------------------------------------
1 | name: codespell
2 | on: [pull_request, push]
3 | jobs:
4 | codespell:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v4
8 | - run: python3 -m pip install codespell
9 | - run: codespell --ignore-words-list="ba,fo,hel,revered,womens"
10 | --skip="./README.*.md,*.svg,*.ai,./benchmarks/snippets.py,./tests,./tools,*.lock"
11 |
--------------------------------------------------------------------------------
/.github/workflows/comment.yml:
--------------------------------------------------------------------------------
1 | name: issues
2 | on:
3 | issues:
4 | types: [closed]
5 | jobs:
6 | add-comment:
7 | runs-on: ubuntu-latest
8 | permissions:
9 | issues: write
10 | steps:
11 | - name: Did I solve your problem?
12 | uses: peter-evans/create-or-update-comment@a35cf36e5301d70b76f316e867e7788a55a31dae
13 | with:
14 | issue-number: ${{ github.event.issue.number }}
15 | body: |
16 | I hope we solved your problem.
17 |
18 | If you like using Rich, you might also enjoy [Textual](https://textual.textualize.io)
19 |
20 |
--------------------------------------------------------------------------------
/.github/workflows/newissue.yml:
--------------------------------------------------------------------------------
1 | name: issues
2 | on:
3 | issues:
4 | types: [opened]
5 | jobs:
6 | add-comment:
7 | runs-on: ubuntu-latest
8 | permissions:
9 | issues: write
10 | steps:
11 | - uses: actions/checkout@v4
12 | with:
13 | ref: master
14 | - name: Install FAQtory
15 | run: pip install FAQtory
16 | - name: Run Suggest
17 | env:
18 | TITLE: ${{ github.event.issue.title }}
19 | run: faqtory suggest "$TITLE" > suggest.md
20 | - name: Read suggest.md
21 | id: suggest
22 | uses: juliangruber/read-file-action@v1
23 | with:
24 | path: ./suggest.md
25 | - name: Suggest FAQ
26 | uses: peter-evans/create-or-update-comment@a35cf36e5301d70b76f316e867e7788a55a31dae
27 | with:
28 | issue-number: ${{ github.event.issue.number }}
29 | body: ${{ steps.suggest.outputs.content }}
30 |
--------------------------------------------------------------------------------
/.github/workflows/pythonpackage.yml:
--------------------------------------------------------------------------------
1 | name: Test Rich module
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | fail-fast: false
10 | matrix:
11 | os: [windows-latest, ubuntu-latest, macos-latest]
12 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
13 | exclude:
14 | - { os: windows-latest, python-version: "3.13" }
15 | defaults:
16 | run:
17 | shell: bash
18 | steps:
19 | - uses: actions/checkout@v4
20 | - name: Set up Python ${{ matrix.python-version }}
21 | uses: actions/setup-python@v5
22 | with:
23 | python-version: ${{ matrix.python-version }}
24 | allow-prereleases: true
25 | - name: Install and configure Poetry
26 | # TODO: workaround for https://github.com/snok/install-poetry/issues/94
27 | uses: snok/install-poetry@v1.3.4
28 | with:
29 | version: 1.3.1
30 | virtualenvs-in-project: true
31 | - name: Install dependencies
32 | run: poetry install
33 | if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
34 | - name: Format check with black
35 | run: |
36 | source $VENV
37 | make format-check
38 | - name: Typecheck with mypy
39 | run: |
40 | source $VENV
41 | make typecheck
42 | - name: Test with pytest (with coverage)
43 | run: |
44 | source $VENV
45 | pytest tests -v --cov=./rich --cov-report=xml:./coverage.xml --cov-report term-missing
46 | - name: Upload code coverage
47 | uses: codecov/codecov-action@v4
48 | with:
49 | token: ${{ secrets.CODECOV_TOKEN }}
50 | file: ./coverage.xml
51 | name: rich
52 | flags: unittests
53 | env_vars: OS,PYTHON
54 |
--------------------------------------------------------------------------------
/.github/workflows/readmechanged.yml:
--------------------------------------------------------------------------------
1 | name: README.md Changed
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | paths:
8 | - 'README.md'
9 |
10 | jobs:
11 | send_notification:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | - name: Send notification to README Authors
16 | env:
17 | GITHUB_TOKEN: ${{ secrets.GHP_README_WORKFLOW }}
18 | GIT_SHA: ${{ github.sha }}
19 | run: |
20 | COMMIT=$(git rev-parse --short "$GIT_SHA")
21 | AUTHORS='@willmcgugan @oleksis @Adilius'
22 | BODY="🤓 $AUTHORS README.md changed 📝. Check the [commit $COMMIT](https://github.com/willmcgugan/rich/commit/$GIT_SHA) 👀"
23 | DISCUSSIONID='MDEwOkRpc2N1c3Npb24zMzI2NzM0'
24 | gh api graphql -H 'GraphQL-Features: discussions_api' -f body="$BODY" -F discussionId="$DISCUSSIONID" -f query='mutation($body: String!, $discussionId: ID!){addDiscussionComment(input:{body: $body , discussionId: $discussionId}){comment{id}}}'
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.ipynb
2 | .pytype
3 | .DS_Store
4 | .vscode
5 | .idea/
6 | mypy_report
7 | docs/build
8 | docs/source/_build
9 | tools/*.txt
10 | playground/
11 |
12 | # Byte-compiled / optimized / DLL files
13 | __pycache__/
14 | *.py[cod]
15 | *$py.class
16 |
17 | # C extensions
18 | *.so
19 |
20 | # Distribution / packaging
21 | .Python
22 | build/
23 | develop-eggs/
24 | dist/
25 | downloads/
26 | eggs/
27 | .eggs/
28 | lib/
29 | lib64/
30 | parts/
31 | sdist/
32 | var/
33 | wheels/
34 | *.egg-info/
35 | .installed.cfg
36 | *.egg
37 | MANIFEST
38 |
39 | # PyInstaller
40 | # Usually these files are written by a python script from a template
41 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
42 | *.manifest
43 | *.spec
44 |
45 | # Installer logs
46 | pip-log.txt
47 | pip-delete-this-directory.txt
48 |
49 | # Unit test / coverage reports
50 | htmlcov/
51 | .tox/
52 | .coverage
53 | .coverage.*
54 | .cache
55 | nosetests.xml
56 | coverage.xml
57 | *.cover
58 | .hypothesis/
59 | .pytest_cache/
60 |
61 | # Translations
62 | *.mo
63 | *.pot
64 |
65 | # Django stuff:
66 | *.log
67 | local_settings.py
68 | db.sqlite3
69 |
70 | # Flask stuff:
71 | instance/
72 | .webassets-cache
73 |
74 | # Scrapy stuff:
75 | .scrapy
76 |
77 | # Sphinx documentation
78 | docs/_build/
79 |
80 | # PyBuilder
81 | target/
82 |
83 | # Jupyter Notebook
84 | .ipynb_checkpoints
85 |
86 | # pyenv
87 | .python-version
88 |
89 | # celery beat schedule file
90 | celerybeat-schedule
91 |
92 | # SageMath parsed files
93 | *.sage.py
94 |
95 | # Environments
96 | .env
97 | .venv
98 | env/
99 | venv/
100 | ENV/
101 | env.bak/
102 | venv.bak/
103 |
104 | # Spyder project settings
105 | .spyderproject
106 | .spyproject
107 |
108 | # Rope project settings
109 | .ropeproject
110 |
111 | # mkdocs documentation
112 | /site
113 |
114 | # mypy
115 | .mypy_cache/
116 |
117 | # airspeed velocity
118 | benchmarks/env/
119 | benchmarks/html/
120 |
121 | sandbox/
122 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | # See https://pre-commit.com for more information
2 | # See https://pre-commit.com/hooks.html for more hooks
3 | exclude: benchmarks/
4 | repos:
5 | - repo: https://github.com/pre-commit/pre-commit-hooks
6 | rev: v4.4.0
7 | hooks:
8 | - id: check-ast
9 | - id: check-builtin-literals
10 | - id: check-case-conflict
11 | - id: check-merge-conflict
12 | - id: check-json
13 | - id: check-toml
14 | - id: check-yaml
15 | - id: end-of-file-fixer
16 | - id: mixed-line-ending
17 | - id: check-vcs-permalinks
18 | - id: check-shebang-scripts-are-executable
19 | - id: trailing-whitespace
20 | - repo: https://github.com/pre-commit/pygrep-hooks
21 | rev: v1.10.0
22 | hooks:
23 | - id: python-no-log-warn
24 | - id: python-use-type-annotations
25 | - id: rst-directive-colons
26 | - id: rst-inline-touching-normal
27 | - repo: https://github.com/hadialqattan/pycln
28 | rev: v2.2.2
29 | hooks:
30 | - id: pycln
31 | args: [--all]
32 | - repo: https://github.com/psf/black-pre-commit-mirror
33 | rev: 23.11.0
34 | hooks:
35 | - id: black
36 | exclude: ^benchmarks/
37 | - repo: https://github.com/PyCQA/isort
38 | rev: 5.12.0
39 | hooks:
40 | - id: isort
41 | name: isort (python)
42 | language_version: "3.11"
43 | args: ["--profile", "black"]
44 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | # Read the Docs configuration file
2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3 |
4 | # Required
5 | version: 2
6 |
7 | # Build documentation in the docs/ directory with Sphinx
8 | sphinx:
9 | configuration: docs/source/conf.py
10 |
11 | # Optionally build your docs in additional formats such as PDF and ePub
12 | formats: all
13 |
14 | build:
15 | os: "ubuntu-24.04"
16 | tools:
17 | python: "3.12"
18 |
19 | python:
20 | install:
21 | - requirements: docs/requirements.txt
22 | - method: pip
23 | path: .
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Will McGugan
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | TERM=unknown pytest --cov-report term-missing --cov=rich tests/ -vv
3 | test-no-cov:
4 | TERM=unknown pytest tests/ -vv
5 | format-check:
6 | black --check .
7 | format:
8 | black .
9 | typecheck:
10 | mypy -p rich --no-incremental
11 | typecheck-report:
12 | mypy -p rich --html-report mypy_report
13 | .PHONY: docs
14 | docs:
15 | cd docs; make html
16 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | ## Security contact information
2 |
3 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
4 |
--------------------------------------------------------------------------------
/assets/logo.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/assets/logo.ai
--------------------------------------------------------------------------------
/assets/logo.txt:
--------------------------------------------------------------------------------
1 | .-----------.
2 | /___/__|__\___\
3 | \ oo /
4 | \_o8o888___ /
5 | /888888(. .)8.
6 | |"""""[H]\ /""|
7 | |-- `. - |^| -|
8 | |__ _)\__|_| _|
9 | _(________)_
10 | (____________)
--------------------------------------------------------------------------------
/asv.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "project": "rich",
4 | "project_url": "https://github.com/Textualize/rich",
5 | "repo": ".",
6 | "repo_subdir": "",
7 | "install_command": [
8 | "in-dir={env_dir} python -mpip install {wheel_file}"
9 | ],
10 | "uninstall_command": [
11 | "return-code=any python -mpip uninstall -y {project}"
12 | ],
13 | "build_command": [
14 | "pip install poetry",
15 | "python setup.py build",
16 | "PIP_NO_BUILD_ISOLATION=false python -mpip wheel --no-deps --no-index -w {build_cache_dir} {build_dir}"
17 | ],
18 | "branches": [
19 | "master"
20 | ],
21 | "html_dir": "./benchmarks/html",
22 | "results_dir": "./benchmarks/results",
23 | "env_dir": "./benchmarks/env",
24 | "dvcs": "git",
25 | "environment_type": "virtualenv",
26 | "install_timeout": 180,
27 | "show_commit_url": "http://github.com/Textualize/rich/commit/",
28 | "pythons": [
29 | "3.10"
30 | ],
31 | "matrix": {
32 | "setuptools": ["59.2.0"]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/asvhashfile:
--------------------------------------------------------------------------------
1 | v10.0.0
2 | v10.2.2
3 | v10.6.0
4 | v10.7.0
5 | v10.8.0
6 | v10.9.0
7 | v11.0.0
8 | v11.1.0
9 | v11.2.0
10 | v12.0.0
11 | v12.0.1
12 | v12.1.0
13 | v12.2.0
14 | v12.3.0
15 | v12.4.0
16 | v12.4.1
17 | v12.4.2
18 | v12.4.3
19 | v12.4.4
20 | v12.5.0
21 | v8.0.0
22 | v9.13.0
23 | v9.5.1
24 |
--------------------------------------------------------------------------------
/benchmarks/README.md:
--------------------------------------------------------------------------------
1 | # Benchmarking Rich
2 |
3 | This directory contains benchmarks, for monitoring the performance of Rich over time.
4 |
5 | View the benchmark dashboard [here](https://textualize.github.io/rich-benchmarks/).
6 |
7 | The benchmarks use a tool called [Airspeed Velocity](https://asv.readthedocs.io/en/stable) (`asv`),
8 | and we've configured it in [asv.conf.json](../asv.conf.json).
9 |
10 | ## Running Benchmarks
11 |
12 | We strongly recommend running `asv run --help` for a full list of options, but
13 | here are some common actions:
14 |
15 | * You can run the benchmarks against the `master` branch with `asv run`.
16 | * To test the most recent commit on your branch `asv run HEAD^!`.
17 | * To generate a static website for browsing the results, run `asv publish`. The resulting HTML can be found in `benchmarks/html`.
18 |
19 | The asv docs have some more examples [here](https://asv.readthedocs.io/en/stable/using.html#benchmarking).
20 |
21 | ## Updating the Benchmark Website
22 |
23 | 1. Ensure any tags you wish to benchmark are included in the file `asvhashfile` at the root of the repo.
24 | 2. Run the benchmarks for those tags by running `asv run HASHFILE:asvhashfile`. This will take several minutes.
25 | 3. Create the HTML locally for those benchmarks by running `asv publish`.
26 | 4. Run `asv preview` to launch a local webserver that will let you preview the benchmarks dashboard. Navigate to the URL this command gives you and check everything looks fine.
27 | 5. Checkout the `rich-benchmarks` repo from [here](https://github.com/Textualize/rich-benchmarks) and `cd` into it.
28 | 6. Copy the HTML you generated earlier into the root of this repo, e.g. `cp -r ../rich/benchmarks/html/* .` (assuming you checked out `rich-benchmarks` alongside `rich` in your filesystem)
29 | 7. When the HTML is merged into `main`, the [benchmark dashboard](https://textualize.github.io/rich-benchmarks/) will be updated automatically via a GitHub Action.
30 |
--------------------------------------------------------------------------------
/benchmarks/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/benchmarks/__init__.py
--------------------------------------------------------------------------------
/benchmarks/results/darrenburns-2022-mbp/machine.json:
--------------------------------------------------------------------------------
1 | {
2 | "arch": "arm64",
3 | "cpu": "Apple M1 Pro",
4 | "machine": "darrenburns-2022-mbp",
5 | "num_cpu": "10",
6 | "os": "Darwin 21.2.0",
7 | "ram": "17179869184",
8 | "version": 1
9 | }
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | alabaster==1.0.0
2 | Sphinx==8.2.3
3 | sphinx-rtd-theme==3.0.2
4 | sphinx-copybutton==0.5.2
5 |
--------------------------------------------------------------------------------
/docs/source/appendix.rst:
--------------------------------------------------------------------------------
1 | Appendix
2 | =========
3 |
4 | .. toctree::
5 | :maxdepth: 3
6 |
7 | appendix/box.rst
8 | appendix/colors.rst
9 |
--------------------------------------------------------------------------------
/docs/source/appendix/box.rst:
--------------------------------------------------------------------------------
1 | .. _appendix_box:
2 |
3 | Box
4 | ===
5 |
6 | Rich has a number of constants that set the box characters used to draw tables and panels. To select a box style import one of the constants below from ``rich.box``. For example::
7 |
8 | from rich import box
9 | table = Table(box=box.SQUARE)
10 |
11 |
12 | .. note::
13 | Some of the box drawing characters will not display correctly on Windows legacy terminal (cmd.exe) with *raster* fonts, and are disabled by default. If you want the full range of box options on Windows legacy terminal, use a *truetype* font and set the ``safe_box`` parameter on the Table class to ``False``.
14 |
15 |
16 | The following table is generated with this command::
17 |
18 | python -m rich.box
19 |
20 | .. image:: ../../images/box.svg
21 |
--------------------------------------------------------------------------------
/docs/source/columns.rst:
--------------------------------------------------------------------------------
1 | Columns
2 | =======
3 |
4 | Rich can render text or other Rich renderables in neat columns with the :class:`~rich.columns.Columns` class. To use, construct a Columns instance with an iterable of renderables and print it to the Console.
5 |
6 | The following example is a very basic clone of the ``ls`` command in OSX / Linux to list directory contents::
7 |
8 | import os
9 | import sys
10 |
11 | from rich import print
12 | from rich.columns import Columns
13 |
14 | if len(sys.argv) < 2:
15 | print("Usage: python columns.py DIRECTORY")
16 | else:
17 | directory = os.listdir(sys.argv[1])
18 | columns = Columns(directory, equal=True, expand=True)
19 | print(columns)
20 |
21 |
22 | See `columns.py `_ for an example which outputs columns containing more than just text.
23 |
24 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | # import os
14 | # import sys
15 | # sys.path.insert(0, os.path.abspath('.'))
16 |
17 |
18 | # -- Project information -----------------------------------------------------
19 |
20 | import sys
21 |
22 | import sphinx_rtd_theme
23 |
24 | if sys.version_info >= (3, 8):
25 | from importlib.metadata import Distribution
26 | else:
27 | from importlib_metadata import Distribution
28 |
29 | html_theme = "sphinx_rtd_theme"
30 |
31 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
32 |
33 | project = "Rich"
34 | copyright = "Will McGugan"
35 | author = "Will McGugan"
36 |
37 | # The full version, including alpha/beta/rc tags
38 | release = Distribution.from_name("rich").version
39 |
40 |
41 | # -- General configuration ---------------------------------------------------
42 |
43 | # Add any Sphinx extension module names here, as strings. They can be
44 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
45 | # ones.
46 | extensions = [
47 | "sphinx.ext.autodoc",
48 | "sphinx.ext.viewcode",
49 | "sphinx.ext.napoleon",
50 | "sphinx.ext.intersphinx",
51 | "sphinx.ext.autosectionlabel",
52 | "sphinx_copybutton",
53 | "sphinx_rtd_theme",
54 | ]
55 |
56 | # Add any paths that contain templates here, relative to this directory.
57 | templates_path = ["_templates"]
58 |
59 | # List of patterns, relative to source directory, that match files and
60 | # directories to ignore when looking for source files.
61 | # This pattern also affects html_static_path and html_extra_path.
62 | exclude_patterns = []
63 |
64 |
65 | # -- Options for HTML output -------------------------------------------------
66 |
67 | # The theme to use for HTML and HTML Help pages. See the documentation for
68 | # a list of builtin themes.
69 | #
70 | # html_theme = "alabaster"
71 |
72 | # Add any paths that contain custom static files (such as style sheets) here,
73 | # relative to this directory. They are copied after the builtin static files,
74 | # so a file named "default.css" will overwrite the builtin "default.css".
75 | html_static_path = ["_static"]
76 |
77 | intersphinx_mapping = {"python": ("http://docs.python.org/3", None)}
78 |
79 | autodoc_typehints = "description"
80 |
81 | html_css_files = [
82 | "https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/fira_code.min.css"
83 | ]
84 |
--------------------------------------------------------------------------------
/docs/source/group.rst:
--------------------------------------------------------------------------------
1 | Render Groups
2 | =============
3 |
4 | The :class:`~rich.console.Group` class allows you to group several renderables together so they may be rendered in a context where only a single renderable may be supplied. For instance, you might want to display several renderables within a :class:`~rich.panel.Panel`.
5 |
6 | To render two panels within a third panel, you would construct a Group with the *child* renderables as positional arguments then wrap the result in another Panel::
7 |
8 | from rich import print
9 | from rich.console import Group
10 | from rich.panel import Panel
11 |
12 | panel_group = Group(
13 | Panel("Hello", style="on blue"),
14 | Panel("World", style="on red"),
15 | )
16 | print(Panel(panel_group))
17 |
18 |
19 | This pattern is nice when you know in advance what renderables will be in a group, but can get awkward if you have a larger number of renderables, especially if they are dynamic. Rich provides a :func:`~rich.console.group` decorator to help with these situations. The decorator builds a group from an iterator of renderables. The following is the equivalent of the previous example using the decorator::
20 |
21 | from rich import print
22 | from rich.console import group
23 | from rich.panel import Panel
24 |
25 | @group()
26 | def get_panels():
27 | yield Panel("Hello", style="on blue")
28 | yield Panel("World", style="on red")
29 |
30 | print(Panel(get_panels()))
31 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | .. Rich documentation master file, created by
2 | sphinx-quickstart on Thu Dec 26 17:03:20 2019.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to Rich's documentation!
7 | ================================
8 |
9 | .. toctree::
10 | :maxdepth: 2
11 | :caption: Contents:
12 |
13 | introduction.rst
14 | console.rst
15 | style.rst
16 | markup.rst
17 | text.rst
18 | highlighting.rst
19 | pretty.rst
20 | logging.rst
21 | traceback.rst
22 | prompt.rst
23 |
24 | columns.rst
25 | group.rst
26 | markdown.rst
27 | padding.rst
28 | panel.rst
29 | progress.rst
30 | syntax.rst
31 | tables.rst
32 | tree.rst
33 | live.rst
34 | layout.rst
35 |
36 | protocol.rst
37 |
38 | reference.rst
39 | appendix.rst
40 |
41 | Indices and tables
42 | ==================
43 |
44 | * :ref:`genindex`
45 | * :ref:`modindex`
46 | * :ref:`search`
47 |
--------------------------------------------------------------------------------
/docs/source/logging.rst:
--------------------------------------------------------------------------------
1 | Logging Handler
2 | ===============
3 |
4 | Rich supplies a :ref:`logging handler` which will format and colorize text written by Python's logging module.
5 |
6 | Here's an example of how to set up a rich logger::
7 |
8 | import logging
9 | from rich.logging import RichHandler
10 |
11 | FORMAT = "%(message)s"
12 | logging.basicConfig(
13 | level="NOTSET", format=FORMAT, datefmt="[%X]", handlers=[RichHandler()]
14 | )
15 |
16 | log = logging.getLogger("rich")
17 | log.info("Hello, World!")
18 |
19 | 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::
20 |
21 | log.error("[bold red blink]Server is shutting down![/]", extra={"markup": True})
22 |
23 | Similarly, the highlighter may be overridden per log message::
24 |
25 | log.error("123 will not be highlighted", extra={"highlighter": None})
26 |
27 |
28 | Handle exceptions
29 | -------------------
30 |
31 | 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::
32 |
33 |
34 | import logging
35 | from rich.logging import RichHandler
36 |
37 | logging.basicConfig(
38 | level="NOTSET",
39 | format="%(message)s",
40 | datefmt="[%X]",
41 | handlers=[RichHandler(rich_tracebacks=True)]
42 | )
43 |
44 | log = logging.getLogger("rich")
45 | try:
46 | print(1 / 0)
47 | except Exception:
48 | log.exception("unable print!")
49 |
50 |
51 | There are a number of other options you can use to configure logging output, see the :class:`~rich.logging.RichHandler` reference for details.
52 |
53 | Suppressing Frames
54 | ------------------
55 |
56 | If you are working with a framework (click, django etc), you may only be interested in seeing the code from your own application within the traceback. You can exclude framework code by setting the `suppress` argument on `Traceback`, `install`, and `Console.print_exception`, which should be a list of modules or str paths.
57 |
58 | Here's how you would exclude `click `_ from Rich exceptions::
59 |
60 | import click
61 | import logging
62 | from rich.logging import RichHandler
63 |
64 | logging.basicConfig(
65 | level="NOTSET",
66 | format="%(message)s",
67 | datefmt="[%X]",
68 | handlers=[RichHandler(rich_tracebacks=True, tracebacks_suppress=[click])]
69 | )
70 |
71 | Suppressed frames will show the line and file only, without any code.
--------------------------------------------------------------------------------
/docs/source/markdown.rst:
--------------------------------------------------------------------------------
1 | Markdown
2 | ========
3 |
4 | Rich can render Markdown to the console. To render markdown, construct a :class:`~rich.markdown.Markdown` object then print it to the console. Markdown is a great way of adding rich content to your command line applications. Here's an example of use::
5 |
6 | MARKDOWN = """
7 | # This is an h1
8 |
9 | Rich can do a pretty *decent* job of rendering markdown.
10 |
11 | 1. This is a list item
12 | 2. This is another list item
13 | """
14 | from rich.console import Console
15 | from rich.markdown import Markdown
16 |
17 | console = Console()
18 | md = Markdown(MARKDOWN)
19 | console.print(md)
20 |
21 | Note that code blocks are rendered with full syntax highlighting!
22 |
23 | You can also use the Markdown class from the command line. The following example displays a readme in the terminal::
24 |
25 | python -m rich.markdown README.md
26 |
27 | Run the following to see the full list of arguments for the markdown command::
28 |
29 | python -m rich.markdown -h
--------------------------------------------------------------------------------
/docs/source/padding.rst:
--------------------------------------------------------------------------------
1 | Padding
2 | =======
3 |
4 | The :class:`~rich.padding.Padding` class may be used to add whitespace around text or other renderable. The following example will print the word "Hello" with a padding of 1 character, so there will be a blank line above and below, and a space on the left and right edges::
5 |
6 | from rich import print
7 | from rich.padding import Padding
8 | test = Padding("Hello", 1)
9 | print(test)
10 |
11 | You can specify the padding on a more granular level by using a tuple of values rather than a single value. A tuple of 2 values sets the top/bottom and left/right padding, whereas a tuple of 4 values sets the padding for top, right, bottom, and left sides. You may recognize this scheme if you are familiar with CSS.
12 |
13 | For example, the following displays 2 blank lines above and below the text, and a padding of 4 spaces on the left and right sides::
14 |
15 | from rich import print
16 | from rich.padding import Padding
17 | test = Padding("Hello", (2, 4))
18 | print(test)
19 |
20 | The Padding class can also accept a ``style`` argument which applies a style to the padding and contents, and an ``expand`` switch which can be set to False to prevent the padding from extending to the full width of the terminal. Here's an example which demonstrates both these arguments::
21 |
22 | from rich import print
23 | from rich.padding import Padding
24 | test = Padding("Hello", (2, 4), style="on blue", expand=False)
25 | print(test)
26 |
27 | Note that, as with all Rich renderables, you can use Padding in any context. For instance, if you want to emphasize an item in a :class:`~rich.table.Table` you could add a Padding object to a row with a padding of 1 and a style of "on red".
28 |
--------------------------------------------------------------------------------
/docs/source/panel.rst:
--------------------------------------------------------------------------------
1 | Panel
2 | =====
3 |
4 | To draw a border around text or other renderable, construct a :class:`~rich.panel.Panel` with the renderable as the first positional argument. Here's an example::
5 |
6 | from rich import print
7 | from rich.panel import Panel
8 | print(Panel("Hello, [red]World!"))
9 |
10 | You can change the style of the panel by setting the ``box`` argument to the Panel constructor. See :ref:`appendix_box` for a list of available box styles.
11 |
12 | Panels will extend to the full width of the terminal. You can make panel *fit* the content by setting ``expand=False`` on the constructor, or by creating the Panel with :meth:`~rich.panel.Panel.fit`. For example::
13 |
14 | from rich import print
15 | from rich.panel import Panel
16 | print(Panel.fit("Hello, [red]World!"))
17 |
18 | The Panel constructor accepts a ``title`` argument which will draw a title on the top of the panel, as well as a ``subtitle`` argument which will draw a subtitle on the bottom of the panel::
19 |
20 | from rich import print
21 | from rich.panel import Panel
22 | print(Panel("Hello, [red]World!", title="Welcome", subtitle="Thank you"))
23 |
24 | See :class:`~rich.panel.Panel` for details how to customize Panels.
25 |
--------------------------------------------------------------------------------
/docs/source/prompt.rst:
--------------------------------------------------------------------------------
1 | Prompt
2 | ======
3 |
4 | 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::
5 |
6 | >>> from rich.prompt import Prompt
7 | >>> name = Prompt.ask("Enter your name")
8 |
9 | The prompt may be given as a string (which may contain :ref:`console_markup` and emoji code) or as a :class:`~rich.text.Text` instance.
10 |
11 | You can set a default value which will be returned if the user presses return without entering any text::
12 |
13 | >>> from rich.prompt import Prompt
14 | >>> name = Prompt.ask("Enter your name", default="Paul Atreides")
15 |
16 | If you supply a list of choices, the prompt will loop until the user enters one of the choices::
17 |
18 | >>> from rich.prompt import Prompt
19 | >>> name = Prompt.ask("Enter your name", choices=["Paul", "Jessica", "Duncan"], default="Paul")
20 |
21 | By default this is case sensitive, but you can set `case_sensitive=False` to make it case insensitive::
22 |
23 | >>> from rich.prompt import Prompt
24 | >>> name = Prompt.ask("Enter your name", choices=["Paul", "Jessica", "Duncan"], default="Paul", case_sensitive=False)
25 |
26 | Now, it would accept "paul" or "Paul" as valid responses.
27 |
28 | In addition to :class:`~rich.prompt.Prompt` which returns strings, you can also use :class:`~rich.prompt.IntPrompt` which asks the user for an integer, and :class:`~rich.prompt.FloatPrompt` for floats.
29 |
30 | The :class:`~rich.prompt.Confirm` class is a specialized prompt which may be used to ask the user a simple yes / no question. Here's an example::
31 |
32 | >>> from rich.prompt import Confirm
33 | >>> is_rich_great = Confirm.ask("Do you like rich?")
34 | >>> assert is_rich_great
35 |
36 | The Prompt class was designed to be customizable via inheritance. See `prompt.py `_ for examples.
37 |
38 | To see some of the prompts in action, run the following command from the command line::
39 |
40 | python -m rich.prompt
41 |
--------------------------------------------------------------------------------
/docs/source/reference.rst:
--------------------------------------------------------------------------------
1 | Reference
2 | =========
3 |
4 | .. toctree::
5 | :maxdepth: 3
6 |
7 | reference/align.rst
8 | reference/bar.rst
9 | reference/color.rst
10 | reference/columns.rst
11 | reference/console.rst
12 | reference/emoji.rst
13 | reference/highlighter.rst
14 | reference/init.rst
15 | reference/json.rst
16 | reference/layout.rst
17 | reference/live.rst
18 | reference/logging.rst
19 | reference/markdown.rst
20 | reference/markup.rst
21 | reference/measure.rst
22 | reference/padding.rst
23 | reference/panel.rst
24 | reference/pretty.rst
25 | reference/progress_bar.rst
26 | reference/progress.rst
27 | reference/prompt.rst
28 | reference/protocol.rst
29 | reference/rule.rst
30 | reference/segment.rst
31 | reference/spinner.rst
32 | reference/status.rst
33 | reference/style.rst
34 | reference/styled.rst
35 | reference/syntax.rst
36 | reference/table.rst
37 | reference/text.rst
38 | reference/theme.rst
39 | reference/traceback.rst
40 | reference/tree.rst
41 | reference/abc.rst
42 |
--------------------------------------------------------------------------------
/docs/source/reference/abc.rst:
--------------------------------------------------------------------------------
1 | rich.abc
2 | ========
3 |
4 | .. automodule:: rich.abc
5 | :members:
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/align.rst:
--------------------------------------------------------------------------------
1 | rich.align
2 | ==========
3 |
4 | .. automodule:: rich.align
5 | :members:
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/bar.rst:
--------------------------------------------------------------------------------
1 | rich.bar
2 | ========
3 |
4 | .. automodule:: rich.bar
5 | :members:
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/color.rst:
--------------------------------------------------------------------------------
1 | rich.color
2 | ==========
3 |
4 | .. automodule:: rich.color
5 | :members:
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/columns.rst:
--------------------------------------------------------------------------------
1 | rich.columns
2 | ============
3 |
4 | .. automodule:: rich.columns
5 | :members:
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/console.rst:
--------------------------------------------------------------------------------
1 | rich.console
2 | ============
3 |
4 | .. automodule:: rich.console
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/emoji.rst:
--------------------------------------------------------------------------------
1 | rich.emoji
2 | ==========
3 |
4 | .. automodule:: rich.emoji
5 | :members: Emoji
6 |
7 |
--------------------------------------------------------------------------------
/docs/source/reference/highlighter.rst:
--------------------------------------------------------------------------------
1 | rich.highlighter
2 | ================
3 |
4 | .. automodule:: rich.highlighter
5 | :members:
6 | :special-members: __call__
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/init.rst:
--------------------------------------------------------------------------------
1 | rich
2 | ====
3 |
4 | .. automodule:: rich
5 | :members:
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/json.rst:
--------------------------------------------------------------------------------
1 | rich.json
2 | =========
3 |
4 | .. automodule:: rich.json
5 | :members:
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/layout.rst:
--------------------------------------------------------------------------------
1 | rich.layout
2 | ===========
3 |
4 | .. automodule:: rich.layout
5 | :members:
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/live.rst:
--------------------------------------------------------------------------------
1 | rich.live
2 | =========
3 |
4 | .. automodule:: rich.live
5 | :members:
--------------------------------------------------------------------------------
/docs/source/reference/logging.rst:
--------------------------------------------------------------------------------
1 | .. _logging:
2 |
3 | rich.logging
4 | ============
5 |
6 | .. automodule:: rich.logging
7 | :members: RichHandler
8 |
9 |
--------------------------------------------------------------------------------
/docs/source/reference/markdown.rst:
--------------------------------------------------------------------------------
1 | rich.markdown
2 | =============
3 |
4 | .. automodule:: rich.markdown
5 | :members:
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/markup.rst:
--------------------------------------------------------------------------------
1 | rich.markup
2 | ===========
3 |
4 | .. automodule:: rich.markup
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/measure.rst:
--------------------------------------------------------------------------------
1 | rich.measure
2 | ============
3 |
4 | .. automodule:: rich.measure
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/padding.rst:
--------------------------------------------------------------------------------
1 | rich.padding
2 | ============
3 |
4 | .. automodule:: rich.padding
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/panel.rst:
--------------------------------------------------------------------------------
1 | rich.panel
2 | ==========
3 |
4 | .. automodule:: rich.panel
5 | :members: Panel
6 |
7 |
--------------------------------------------------------------------------------
/docs/source/reference/pretty.rst:
--------------------------------------------------------------------------------
1 | rich.pretty
2 | ===========
3 |
4 | .. automodule:: rich.pretty
5 | :members:
6 |
7 |
--------------------------------------------------------------------------------
/docs/source/reference/progress.rst:
--------------------------------------------------------------------------------
1 | rich.progress
2 | =============
3 |
4 | .. automodule:: rich.progress
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/progress_bar.rst:
--------------------------------------------------------------------------------
1 | rich.progress_bar
2 | =================
3 |
4 | .. automodule:: rich.progress_bar
5 | :members:
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/prompt.rst:
--------------------------------------------------------------------------------
1 | rich.prompt
2 | ===========
3 |
4 | .. automodule:: rich.prompt
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/protocol.rst:
--------------------------------------------------------------------------------
1 | rich.protocol
2 | =============
3 |
4 | .. automodule:: rich.protocol
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/rule.rst:
--------------------------------------------------------------------------------
1 | rich.rule
2 | =========
3 |
4 | .. automodule:: rich.rule
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/segment.rst:
--------------------------------------------------------------------------------
1 | rich.segment
2 | ============
3 |
4 | .. automodule:: rich.segment
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/spinner.rst:
--------------------------------------------------------------------------------
1 | rich.spinner
2 | ============
3 |
4 | .. automodule:: rich.spinner
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/status.rst:
--------------------------------------------------------------------------------
1 | rich.status
2 | ============
3 |
4 | .. automodule:: rich.status
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/style.rst:
--------------------------------------------------------------------------------
1 | rich.style
2 | ==========
3 |
4 | .. automodule:: rich.style
5 | :members:
6 | :special-members: __call__
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/styled.rst:
--------------------------------------------------------------------------------
1 | rich.styled
2 | ===========
3 |
4 | .. automodule:: rich.styled
5 | :members:
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/reference/syntax.rst:
--------------------------------------------------------------------------------
1 | rich.syntax
2 | ===========
3 |
4 | .. automodule:: rich.syntax
5 | :members: Syntax
6 |
--------------------------------------------------------------------------------
/docs/source/reference/table.rst:
--------------------------------------------------------------------------------
1 | rich.table
2 | ==========
3 |
4 | .. automodule:: rich.table
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/reference/text.rst:
--------------------------------------------------------------------------------
1 | rich.text
2 | =========
3 |
4 | .. automodule:: rich.text
5 | :members: Text, TextType
6 |
7 |
--------------------------------------------------------------------------------
/docs/source/reference/theme.rst:
--------------------------------------------------------------------------------
1 | rich.theme
2 | ==========
3 |
4 | .. automodule:: rich.theme
5 | :members: Theme
6 |
7 |
--------------------------------------------------------------------------------
/docs/source/reference/traceback.rst:
--------------------------------------------------------------------------------
1 | rich.traceback
2 | ==============
3 |
4 | .. automodule:: rich.traceback
5 | :members: Traceback, install
6 |
7 |
--------------------------------------------------------------------------------
/docs/source/reference/tree.rst:
--------------------------------------------------------------------------------
1 | rich.tree
2 | =========
3 |
4 | .. automodule:: rich.tree
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/syntax.rst:
--------------------------------------------------------------------------------
1 | Syntax
2 | ======
3 |
4 | Rich can syntax highlight various programming languages with line numbers.
5 |
6 | To syntax highlight code, construct a :class:`~rich.syntax.Syntax` object and print it to the console. Here's an example::
7 |
8 | from rich.console import Console
9 | from rich.syntax import Syntax
10 |
11 | console = Console()
12 | with open("syntax.py", "rt") as code_file:
13 | syntax = Syntax(code_file.read(), "python")
14 | console.print(syntax)
15 |
16 | 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::
17 |
18 |
19 | from rich.console import Console
20 | from rich.syntax import Syntax
21 |
22 | console = Console()
23 | syntax = Syntax.from_path("syntax.py")
24 | console.print(syntax)
25 |
26 |
27 | Line numbers
28 | ------------
29 |
30 | If you set ``line_numbers=True``, Rich will render a column for line numbers::
31 |
32 | syntax = Syntax.from_path("syntax.py", line_numbers=True)
33 |
34 |
35 | Theme
36 | -----
37 |
38 | The Syntax constructor (and :meth:`~rich.syntax.Syntax.from_path`) accept a ``theme`` attribute which should be the name of a `Pygments theme `_. It may also be one of the special case theme names "ansi_dark" or "ansi_light" which will use the color theme configured by the terminal.
39 |
40 |
41 | Background color
42 | ----------------
43 |
44 | You can override the background color from the theme by supplying a ``background_color`` argument to the constructor. This should be a string in the same format a style definition accepts, e.g. "red", "#ff0000", "rgb(255,0,0)" etc. You may also set the special value "default" which will use the default background color set in the terminal.
45 |
46 |
47 | Syntax CLI
48 | ----------
49 |
50 | You can use this class from the command line. Here's how you would syntax highlight a file called "syntax.py"::
51 |
52 | python -m rich.syntax syntax.py
53 |
54 | For the full list of arguments, run the following::
55 |
56 | python -m rich.syntax -h
57 |
58 |
--------------------------------------------------------------------------------
/docs/source/text.rst:
--------------------------------------------------------------------------------
1 | .. _rich_text:
2 |
3 | Rich Text
4 | =========
5 |
6 | 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.
7 |
8 | 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.
9 |
10 | 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::
11 |
12 | from rich.console import Console
13 | from rich.text import Text
14 |
15 | console = Console()
16 | text = Text("Hello, World!")
17 | text.stylize("bold magenta", 0, 6)
18 | console.print(text)
19 |
20 | This will print "Hello, World!" to the terminal, with the first word in bold magenta.
21 |
22 | Alternatively, you can construct styled text by calling :meth:`~rich.text.Text.append` to add a string and style to the end of the Text. Here's an example::
23 |
24 | text = Text()
25 | text.append("Hello", style="bold magenta")
26 | text.append(" World!")
27 | console.print(text)
28 |
29 | 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::
30 |
31 | text = Text.from_ansi("\033[1;35mHello\033[0m, World!")
32 | console.print(text.spans)
33 |
34 | 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 following example is equivalent to the ANSI example above::
35 |
36 | text = Text.assemble(("Hello", "bold magenta"), ", World!")
37 | console.print(text)
38 |
39 | You can apply a style to given words in the text with :meth:`~rich.text.Text.highlight_words` or for ultimate control call :meth:`~rich.text.Text.highlight_regex` to highlight text matching a *regular expression*.
40 |
41 |
42 | Text attributes
43 | ~~~~~~~~~~~~~~~
44 |
45 | The Text class has a number of parameters you can set on the constructor to modify how the text is displayed.
46 |
47 | - ``justify`` should be "left", "center", "right", or "full", and will override default justify behavior.
48 | - ``overflow`` should be "fold", "crop", or "ellipsis", and will override default overflow.
49 | - ``no_wrap`` prevents wrapping if the text is longer then the available width.
50 | - ``tab_size`` Sets the number of characters in a tab.
51 |
52 | A Text instance may be used in place of a plain string virtually everywhere in the Rich API, which gives you a lot of control in how text renders within other Rich renderables. For instance, the following example right aligns text within a :class:`~rich.panel.Panel`::
53 |
54 | from rich import print
55 | from rich.panel import Panel
56 | from rich.text import Text
57 | panel = Panel(Text("Hello", justify="right"))
58 | print(panel)
59 |
60 |
61 |
--------------------------------------------------------------------------------
/docs/source/tree.rst:
--------------------------------------------------------------------------------
1 | Tree
2 | ====
3 |
4 | Rich has a :class:`~rich.tree.Tree` class which can generate a tree view in the terminal. A tree view is a great way of presenting the contents of a filesystem or any other hierarchical data. Each branch of the tree can have a label which may be text or any other Rich renderable.
5 |
6 | Run the following command to see a demonstration of a Rich tree::
7 |
8 | python -m rich.tree
9 |
10 | The following code creates and prints a tree with a simple text label::
11 |
12 | from rich.tree import Tree
13 | from rich import print
14 |
15 | tree = Tree("Rich Tree")
16 | print(tree)
17 |
18 | With only a single ``Tree`` instance this will output nothing more than the text "Rich Tree". Things get more interesting when we call :meth:`~rich.tree.Tree.add` to add more branches to the Tree. The following code adds two more branches::
19 |
20 | tree.add("foo")
21 | tree.add("bar")
22 | print(tree)
23 |
24 | The tree will now have two branches connected to the original tree with guide lines.
25 |
26 | When you call :meth:`~rich.tree.Tree.add` a new Tree instance is returned. You can use this instance to add more branches to, and build up a more complex tree. Let's add a few more levels to the tree::
27 |
28 | baz_tree = tree.add("baz")
29 | baz_tree.add("[red]Red").add("[green]Green").add("[blue]Blue")
30 | print(tree)
31 |
32 |
33 | Tree Styles
34 | ~~~~~~~~~~~
35 |
36 | The Tree constructor and :meth:`~rich.tree.Tree.add` method allows you to specify a ``style`` argument which sets a style for the entire branch, and ``guide_style`` which sets the style for the guide lines. These styles are inherited by the branches and will apply to any sub-trees as well.
37 |
38 | If you set ``guide_style`` to bold, Rich will select the thicker variations of unicode line characters. Similarly, if you select the "underline2" style you will get double line style of unicode characters.
39 |
40 |
41 | Examples
42 | ~~~~~~~~
43 |
44 | For a more practical demonstration, see `tree.py `_ which can generate a tree view of a directory in your hard drive.
45 |
46 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | This directory contains various demonstrations various Rich features. To run them, make sure Rich is installed, then run the example you want with `python example.py` on the command line. For example, `python justify.py`.
4 |
5 | Be sure to check the source!
6 |
--------------------------------------------------------------------------------
/examples/attrs.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | try:
4 | import attr
5 | except ImportError:
6 | print("This example requires attrs library")
7 | print("pip install attrs")
8 | raise SystemExit()
9 |
10 |
11 | @attr.define
12 | class Point3D:
13 | x: float
14 | y: float
15 | z: float = 0
16 |
17 |
18 | @attr.define
19 | class Triangle:
20 | point1: Point3D
21 | point2: Point3D
22 | point3: Point3D
23 |
24 |
25 | @attr.define
26 | class Model:
27 | name: str
28 | triangles: List[Triangle] = attr.Factory(list)
29 |
30 |
31 | if __name__ == "__main__":
32 | model = Model(
33 | name="Alien#1",
34 | triangles=[
35 | Triangle(
36 | Point3D(x=20, y=50),
37 | Point3D(x=50, y=15, z=-45.34),
38 | Point3D(3.1426, 83.2323, -16),
39 | )
40 | ],
41 | )
42 |
43 | from rich.console import Console
44 | from rich.pretty import Pretty
45 | from rich.table import Column, Table
46 | from rich.text import Text
47 |
48 | console = Console()
49 |
50 | table = Table("attrs *with* Rich", Column(Text.from_markup("attrs *without* Rich")))
51 |
52 | table.add_row(Pretty(model), repr(model))
53 | console.print(table)
54 |
--------------------------------------------------------------------------------
/examples/bars.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | Use Bar to renderer a sort-of circle.
4 |
5 | """
6 | import math
7 |
8 | from rich.align import Align
9 | from rich.bar import Bar
10 | from rich.color import Color
11 | from rich import print
12 |
13 |
14 | SIZE = 40
15 |
16 | for row in range(SIZE):
17 | y = (row / (SIZE - 1)) * 2 - 1
18 | x = math.sqrt(1 - y * y)
19 | color = Color.from_rgb((1 + y) * 127.5, 0, 0)
20 | bar = Bar(2, width=SIZE * 2, begin=1 - x, end=1 + x, color=color)
21 | print(Align.center(bar))
22 |
--------------------------------------------------------------------------------
/examples/columns.py:
--------------------------------------------------------------------------------
1 | """
2 | This example shows how to display content in columns.
3 |
4 | The data is pulled from https://randomuser.me
5 | """
6 |
7 | import json
8 | from urllib.request import urlopen
9 |
10 | from rich.console import Console
11 | from rich.columns import Columns
12 | from rich.panel import Panel
13 |
14 |
15 | def get_content(user):
16 | """Extract text from user dict."""
17 | country = user["location"]["country"]
18 | name = f"{user['name']['first']} {user['name']['last']}"
19 | return f"[b]{name}[/b]\n[yellow]{country}"
20 |
21 |
22 | console = Console()
23 |
24 |
25 | users = json.loads(urlopen("https://randomuser.me/api/?results=30").read())["results"]
26 | console.print(users, overflow="ignore", crop=False)
27 | user_renderables = [Panel(get_content(user), expand=True) for user in users]
28 | console.print(Columns(user_renderables))
29 |
--------------------------------------------------------------------------------
/examples/cp_progress.py:
--------------------------------------------------------------------------------
1 | """
2 | A very minimal `cp` clone that displays a progress bar.
3 | """
4 | import os
5 | import shutil
6 | import sys
7 |
8 | from rich.progress import Progress
9 |
10 | if __name__ == "__main__":
11 | if len(sys.argv) == 3:
12 | with Progress() as progress:
13 | desc = os.path.basename(sys.argv[1])
14 | with progress.open(sys.argv[1], "rb", description=desc) as src:
15 | with open(sys.argv[2], "wb") as dst:
16 | shutil.copyfileobj(src, dst)
17 | else:
18 | print("Copy a file with a progress bar.")
19 | print("Usage:\n\tpython cp_progress.py SRC DST")
20 |
--------------------------------------------------------------------------------
/examples/downloader.py:
--------------------------------------------------------------------------------
1 | """
2 | A rudimentary URL downloader (like wget or curl) to demonstrate Rich progress bars.
3 | """
4 |
5 | import os.path
6 | import sys
7 | from concurrent.futures import ThreadPoolExecutor
8 | import signal
9 | from functools import partial
10 | from threading import Event
11 | from typing import Iterable
12 | from urllib.request import urlopen
13 |
14 | from rich.progress import (
15 | BarColumn,
16 | DownloadColumn,
17 | Progress,
18 | TaskID,
19 | TextColumn,
20 | TimeRemainingColumn,
21 | TransferSpeedColumn,
22 | )
23 |
24 | progress = Progress(
25 | TextColumn("[bold blue]{task.fields[filename]}", justify="right"),
26 | BarColumn(bar_width=None),
27 | "[progress.percentage]{task.percentage:>3.1f}%",
28 | "•",
29 | DownloadColumn(),
30 | "•",
31 | TransferSpeedColumn(),
32 | "•",
33 | TimeRemainingColumn(),
34 | )
35 |
36 |
37 | done_event = Event()
38 |
39 |
40 | def handle_sigint(signum, frame):
41 | done_event.set()
42 |
43 |
44 | signal.signal(signal.SIGINT, handle_sigint)
45 |
46 |
47 | def copy_url(task_id: TaskID, url: str, path: str) -> None:
48 | """Copy data from a url to a local file."""
49 | progress.console.log(f"Requesting {url}")
50 | response = urlopen(url)
51 | # This will break if the response doesn't contain content length
52 | progress.update(task_id, total=int(response.info()["Content-length"]))
53 | with open(path, "wb") as dest_file:
54 | progress.start_task(task_id)
55 | for data in iter(partial(response.read, 32768), b""):
56 | dest_file.write(data)
57 | progress.update(task_id, advance=len(data))
58 | if done_event.is_set():
59 | return
60 | progress.console.log(f"Downloaded {path}")
61 |
62 |
63 | def download(urls: Iterable[str], dest_dir: str):
64 | """Download multiple files to the given directory."""
65 |
66 | with progress:
67 | with ThreadPoolExecutor(max_workers=4) as pool:
68 | for url in urls:
69 | filename = url.split("/")[-1]
70 | dest_path = os.path.join(dest_dir, filename)
71 | task_id = progress.add_task("download", filename=filename, start=False)
72 | pool.submit(copy_url, task_id, url, dest_path)
73 |
74 |
75 | if __name__ == "__main__":
76 | # Try with https://releases.ubuntu.com/noble/ubuntu-24.04-desktop-amd64.iso
77 | # and https://releases.ubuntu.com/noble/ubuntu-24.04-live-server-amd64.iso
78 | if sys.argv[1:]:
79 | download(sys.argv[1:], "./")
80 | else:
81 | print("Usage:\n\tpython downloader.py URL1 URL2 URL3 (etc)")
82 |
--------------------------------------------------------------------------------
/examples/exception.py:
--------------------------------------------------------------------------------
1 | """
2 | Basic example to show how to print an traceback of an exception
3 | """
4 | from typing import List, Tuple
5 |
6 | from rich.console import Console
7 |
8 | console = Console()
9 |
10 |
11 | def divide_by(number: float, divisor: float) -> float:
12 | """Divide any number by zero."""
13 | # Will throw a ZeroDivisionError if divisor is 0
14 | result = number / divisor
15 | return result
16 |
17 |
18 | def divide_all(divides: List[Tuple[float, float]]) -> None:
19 | """Do something impossible every day."""
20 |
21 | for number, divisor in divides:
22 | console.print(f"dividing {number} by {divisor}")
23 | try:
24 | result = divide_by(number, divisor)
25 | except Exception:
26 | console.print_exception(extra_lines=8, show_locals=True)
27 | else:
28 | console.print(f" = {result}")
29 |
30 |
31 | DIVIDES = [
32 | (1000, 200),
33 | (10000, 500),
34 | (1, 0),
35 | (0, 1000000),
36 | (3.1427, 2),
37 | (888, 0),
38 | (2**32, 2**16),
39 | ]
40 |
41 | divide_all(DIVIDES)
42 |
--------------------------------------------------------------------------------
/examples/export.py:
--------------------------------------------------------------------------------
1 | """
2 | Demonstrates export console output
3 | """
4 |
5 | from rich.console import Console
6 | from rich.table import Table
7 |
8 | console = Console(record=True)
9 |
10 |
11 | def print_table():
12 | table = Table(title="Star Wars Movies")
13 |
14 | table.add_column("Released", style="cyan", no_wrap=True)
15 | table.add_column("Title", style="magenta")
16 | table.add_column("Box Office", justify="right", style="green")
17 |
18 | table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690")
19 | table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
20 | table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889")
21 | table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889")
22 |
23 | console.print(table)
24 |
25 |
26 | # Prints table
27 | print_table()
28 |
29 | # Get console output as text
30 | file1 = "table_export_plaintext.txt"
31 | text = console.export_text()
32 | with open(file1, "w") as file:
33 | file.write(text)
34 | print(f"Exported console output as plain text to {file1}")
35 |
36 | # Calling print_table again because console output buffer
37 | # is flushed once export function is called
38 | print_table()
39 |
40 | # Get console output as html
41 | # use clear=False so output is not flushed after export
42 | file2 = "table_export_html.html"
43 | html = console.export_html(clear=False)
44 | with open(file2, "w") as file:
45 | file.write(html)
46 | print(f"Exported console output as html to {file2}")
47 |
48 | # Export text output to table_export.txt
49 | file3 = "table_export_plaintext2.txt"
50 | console.save_text(file3, clear=False)
51 | print(f"Exported console output as plain text to {file3}")
52 |
53 | # Export html output to table_export.html
54 | file4 = "table_export_html2.html"
55 | console.save_html(file4)
56 | print(f"Exported console output as html to {file4}")
57 |
--------------------------------------------------------------------------------
/examples/file_progress.py:
--------------------------------------------------------------------------------
1 | from time import sleep
2 | from urllib.request import urlopen
3 |
4 | from rich.progress import wrap_file
5 |
6 | # Read a URL with urlopen
7 | response = urlopen("https://www.textualize.io")
8 | # Get the size from the headers
9 | size = int(response.headers["Content-Length"])
10 |
11 | # Wrap the response so that it update progress
12 |
13 | with wrap_file(response, size) as file:
14 | for line in file:
15 | print(line.decode("utf-8"), end="")
16 | sleep(0.1)
17 |
--------------------------------------------------------------------------------
/examples/group.py:
--------------------------------------------------------------------------------
1 | from rich import print
2 | from rich.console import Group
3 | from rich.panel import Panel
4 |
5 | panel_group = Group(
6 | Panel("Hello", style="on blue"),
7 | Panel("World", style="on red"),
8 | )
9 | print(Panel(panel_group))
10 |
--------------------------------------------------------------------------------
/examples/group2.py:
--------------------------------------------------------------------------------
1 | from rich import print
2 | from rich.console import group
3 | from rich.panel import Panel
4 |
5 |
6 | @group()
7 | def get_panels():
8 | yield Panel("Hello", style="on blue")
9 | yield Panel("World", style="on red")
10 |
11 |
12 | print(Panel(get_panels()))
13 |
--------------------------------------------------------------------------------
/examples/highlighter.py:
--------------------------------------------------------------------------------
1 | """
2 | This example demonstrates a simple text highlighter.
3 | """
4 |
5 | from rich.console import Console
6 | from rich.highlighter import RegexHighlighter
7 | from rich.theme import Theme
8 |
9 |
10 | class EmailHighlighter(RegexHighlighter):
11 | """Apply style to anything that looks like an email."""
12 |
13 | base_style = "example."
14 | highlights = [r"(?P[\w-]+@([\w-]+\.)+[\w-]+)"]
15 |
16 |
17 | theme = Theme({"example.email": "bold magenta"})
18 | console = Console(highlighter=EmailHighlighter(), theme=theme)
19 |
20 | console.print("Send funds to money@example.org")
21 |
--------------------------------------------------------------------------------
/examples/jobs.py:
--------------------------------------------------------------------------------
1 | from time import sleep
2 | from rich.panel import Panel
3 | from rich.progress import Progress
4 |
5 |
6 | JOBS = [100, 150, 25, 70, 110, 90]
7 |
8 | progress = Progress(auto_refresh=False)
9 | master_task = progress.add_task("overall", total=sum(JOBS))
10 | jobs_task = progress.add_task("jobs")
11 |
12 | progress.console.print(
13 | Panel(
14 | "[bold blue]A demonstration of progress with a current task and overall progress.",
15 | padding=1,
16 | )
17 | )
18 |
19 | with progress:
20 | for job_no, job in enumerate(JOBS):
21 | progress.log(f"Starting job #{job_no}")
22 | sleep(0.2)
23 | progress.reset(jobs_task, total=job, description=f"job [bold yellow]#{job_no}")
24 | progress.start_task(jobs_task)
25 | for wait in progress.track(range(job), task_id=jobs_task):
26 | sleep(0.01)
27 | progress.advance(master_task, job)
28 | progress.log(f"Job #{job_no} is complete")
29 | progress.log(
30 | Panel(":sparkle: All done! :sparkle:", border_style="green", padding=1)
31 | )
32 |
--------------------------------------------------------------------------------
/examples/justify.py:
--------------------------------------------------------------------------------
1 | """
2 | This example demonstrates the justify argument to print.
3 | """
4 |
5 | from rich.console import Console
6 |
7 | console = Console(width=20)
8 |
9 | style = "bold white on blue"
10 | console.print("Rich", style=style)
11 | console.print("Rich", style=style, justify="left")
12 | console.print("Rich", style=style, justify="center")
13 | console.print("Rich", style=style, justify="right")
14 |
--------------------------------------------------------------------------------
/examples/justify2.py:
--------------------------------------------------------------------------------
1 | """
2 | This example demonstrates the justify argument to print.
3 | """
4 |
5 | from rich.console import Console
6 | from rich.panel import Panel
7 |
8 | console = Console(width=20)
9 |
10 | style = "bold white on blue"
11 | panel = Panel("Rich", style="on red", expand=False)
12 | console.print(panel, style=style)
13 | console.print(panel, style=style, justify="left")
14 | console.print(panel, style=style, justify="center")
15 | console.print(panel, style=style, justify="right")
16 |
--------------------------------------------------------------------------------
/examples/layout.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | Demonstrates a dynamic Layout
4 |
5 | """
6 |
7 | from datetime import datetime
8 |
9 | from time import sleep
10 |
11 | from rich.align import Align
12 | from rich.console import Console
13 | from rich.layout import Layout
14 | from rich.live import Live
15 | from rich.text import Text
16 |
17 | console = Console()
18 | layout = Layout()
19 |
20 | layout.split(
21 | Layout(name="header", size=1),
22 | Layout(ratio=1, name="main"),
23 | Layout(size=10, name="footer"),
24 | )
25 |
26 | layout["main"].split_row(Layout(name="side"), Layout(name="body", ratio=2))
27 |
28 | layout["side"].split(Layout(), Layout())
29 |
30 | layout["body"].update(
31 | Align.center(
32 | Text(
33 | """This is a demonstration of rich.Layout\n\nHit Ctrl+C to exit""",
34 | justify="center",
35 | ),
36 | vertical="middle",
37 | )
38 | )
39 |
40 |
41 | class Clock:
42 | """Renders the time in the center of the screen."""
43 |
44 | def __rich__(self) -> Text:
45 | return Text(datetime.now().ctime(), style="bold magenta", justify="center")
46 |
47 |
48 | layout["header"].update(Clock())
49 |
50 | with Live(layout, screen=True, redirect_stderr=False) as live:
51 | try:
52 | while True:
53 | sleep(1)
54 | except KeyboardInterrupt:
55 | pass
56 |
--------------------------------------------------------------------------------
/examples/link.py:
--------------------------------------------------------------------------------
1 | from rich import print
2 |
3 | print("If your terminal supports links, the following text should be clickable:")
4 | print("[link=https://www.willmcgugan.com][i]Visit [red]my[/red][/i] [yellow]Blog[/]")
5 |
--------------------------------------------------------------------------------
/examples/listdir.py:
--------------------------------------------------------------------------------
1 | """
2 | A very simple `ls` clone.
3 |
4 | If your terminal supports hyperlinks you should be able to launch files by clicking the filename
5 | (usually with cmd / ctrl).
6 |
7 | """
8 |
9 | import os
10 | import sys
11 |
12 | from rich import print
13 | from rich.columns import Columns
14 | from rich.text import Text
15 |
16 | try:
17 | root_path = sys.argv[1]
18 | except IndexError:
19 | print("Usage: python listdir.py DIRECTORY")
20 | else:
21 |
22 | def make_filename_text(filename):
23 | path = os.path.abspath(os.path.join(root_path, filename))
24 | text = Text(filename, style="bold blue" if os.path.isdir(path) else "default")
25 | text.stylize(f"link file://{path}")
26 | text.highlight_regex(r"\..*?$", "bold")
27 | return text
28 |
29 | filenames = [
30 | filename for filename in os.listdir(root_path) if not filename.startswith(".")
31 | ]
32 | filenames.sort(key=lambda filename: filename.lower())
33 | filename_text = [make_filename_text(filename) for filename in filenames]
34 | columns = Columns(filename_text, equal=True, column_first=True)
35 | print(columns)
36 |
--------------------------------------------------------------------------------
/examples/live_progress.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | Demonstrates the use of multiple Progress instances in a single Live display.
4 |
5 | """
6 |
7 | from time import sleep
8 |
9 | from rich.live import Live
10 | from rich.panel import Panel
11 | from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn
12 | from rich.table import Table
13 |
14 |
15 | job_progress = Progress(
16 | "{task.description}",
17 | SpinnerColumn(),
18 | BarColumn(),
19 | TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
20 | )
21 | job1 = job_progress.add_task("[green]Cooking")
22 | job2 = job_progress.add_task("[magenta]Baking", total=200)
23 | job3 = job_progress.add_task("[cyan]Mixing", total=400)
24 |
25 | total = sum(task.total for task in job_progress.tasks)
26 | overall_progress = Progress()
27 | overall_task = overall_progress.add_task("All Jobs", total=int(total))
28 |
29 | progress_table = Table.grid()
30 | progress_table.add_row(
31 | Panel.fit(
32 | overall_progress, title="Overall Progress", border_style="green", padding=(2, 2)
33 | ),
34 | Panel.fit(job_progress, title="[b]Jobs", border_style="red", padding=(1, 2)),
35 | )
36 |
37 | with Live(progress_table, refresh_per_second=10):
38 | while not overall_progress.finished:
39 | sleep(0.1)
40 | for job in job_progress.tasks:
41 | if not job.finished:
42 | job_progress.advance(job.id)
43 |
44 | completed = sum(task.completed for task in job_progress.tasks)
45 | overall_progress.update(overall_task, completed=completed)
46 |
--------------------------------------------------------------------------------
/examples/log.py:
--------------------------------------------------------------------------------
1 | """
2 | A simulation of Rich console logging.
3 | """
4 |
5 | import time
6 | from rich.console import Console
7 | from rich.style import Style
8 | from rich.theme import Theme
9 | from rich.highlighter import RegexHighlighter
10 |
11 |
12 | class RequestHighlighter(RegexHighlighter):
13 | base_style = "req."
14 | highlights = [
15 | r"^(?P\w+) (?P\w+) (?P\S+) (?P\w+) (?P\[.+\])$",
16 | r"\/(?P\w+\..{3,4})",
17 | ]
18 |
19 |
20 | theme = Theme(
21 | {
22 | "req.protocol": Style.parse("dim bold green"),
23 | "req.method": Style.parse("bold cyan"),
24 | "req.path": Style.parse("magenta"),
25 | "req.filename": Style.parse("bright_magenta"),
26 | "req.result": Style.parse("yellow"),
27 | "req.stats": Style.parse("dim"),
28 | }
29 | )
30 | console = Console(theme=theme)
31 |
32 | console.log("Server starting...")
33 | console.log("Serving on http://127.0.0.1:8000")
34 |
35 | time.sleep(1)
36 |
37 | request_highlighter = RequestHighlighter()
38 |
39 | console.log(
40 | request_highlighter("HTTP GET /foo/bar/baz/egg.html 200 [0.57, 127.0.0.1:59076]"),
41 | )
42 |
43 | console.log(
44 | request_highlighter(
45 | "HTTP GET /foo/bar/baz/background.jpg 200 [0.57, 127.0.0.1:59076]"
46 | ),
47 | )
48 |
49 |
50 | time.sleep(1)
51 |
52 |
53 | def test_locals():
54 | foo = (1, 2, 3)
55 | movies = ["Deadpool", "Rise of the Skywalker"]
56 | console = Console()
57 |
58 | console.log(
59 | "[b]JSON[/b] RPC [i]batch[/i]",
60 | [
61 | {"jsonrpc": "2.0", "method": "sum", "params": [1, 2, 4], "id": "1"},
62 | {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
63 | {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "2"},
64 | {"foo": "boo"},
65 | {
66 | "jsonrpc": "2.0",
67 | "method": "foo.get",
68 | "params": {"name": "myself", "enable": False, "grommits": None},
69 | "id": "5",
70 | },
71 | {"jsonrpc": "2.0", "method": "get_data", "id": "9"},
72 | ],
73 | log_locals=True,
74 | )
75 |
76 |
77 | test_locals()
78 |
--------------------------------------------------------------------------------
/examples/overflow.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 | from rich.console import Console, OverflowMethod
3 |
4 | console = Console(width=14)
5 | supercali = "supercalifragilisticexpialidocious"
6 |
7 | overflow_methods: List[OverflowMethod] = ["fold", "crop", "ellipsis"]
8 | for overflow in overflow_methods:
9 | console.rule(overflow)
10 | console.print(supercali, overflow=overflow, style="bold blue")
11 | console.print()
12 |
--------------------------------------------------------------------------------
/examples/padding.py:
--------------------------------------------------------------------------------
1 | from rich import print
2 | from rich.padding import Padding
3 |
4 | test = Padding("Hello", (2, 4), style="on blue", expand=False)
5 | print(test)
6 |
--------------------------------------------------------------------------------
/examples/print_calendar.py:
--------------------------------------------------------------------------------
1 | """
2 | Builds calendar layout using Columns and Tables.
3 | Usage:
4 | python print_calendar.py [YEAR]
5 | Example:
6 | python print_calendar.py 2021
7 | """
8 | import argparse
9 | import calendar
10 | from datetime import datetime
11 |
12 | from rich.align import Align
13 | from rich import box
14 | from rich.columns import Columns
15 | from rich.console import Console
16 | from rich.table import Table
17 | from rich.text import Text
18 |
19 |
20 | def print_calendar(year):
21 | """Print a calendar for a given year."""
22 |
23 | today = datetime.today()
24 | year = int(year)
25 | cal = calendar.Calendar()
26 | today_tuple = today.day, today.month, today.year
27 |
28 | tables = []
29 |
30 | for month in range(1, 13):
31 | table = Table(
32 | title=f"{calendar.month_name[month]} {year}",
33 | style="green",
34 | box=box.SIMPLE_HEAVY,
35 | padding=0,
36 | )
37 |
38 | for week_day in cal.iterweekdays():
39 | table.add_column(
40 | "{:.3}".format(calendar.day_name[week_day]), justify="right"
41 | )
42 |
43 | month_days = cal.monthdayscalendar(year, month)
44 | for weekdays in month_days:
45 | days = []
46 | for index, day in enumerate(weekdays):
47 | day_label = Text(str(day or ""), style="magenta")
48 | if index in (5, 6):
49 | day_label.stylize("blue")
50 | if day and (day, month, year) == today_tuple:
51 | day_label.stylize("white on dark_red")
52 | days.append(day_label)
53 | table.add_row(*days)
54 |
55 | tables.append(Align.center(table))
56 |
57 | console = Console()
58 | columns = Columns(tables, padding=1, expand=True)
59 | console.rule(str(year))
60 | console.print()
61 | console.print(columns)
62 | console.rule(str(year))
63 |
64 |
65 | if __name__ == "__main__":
66 | parser = argparse.ArgumentParser(description="Rich calendar")
67 | parser.add_argument("year", metavar="year", type=int)
68 | args = parser.parse_args()
69 |
70 | print_calendar(args.year)
71 |
--------------------------------------------------------------------------------
/examples/rainbow.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | This example demonstrates how to write a custom highlighter.
4 |
5 | """
6 |
7 | from random import randint
8 |
9 | from rich import print
10 | from rich.highlighter import Highlighter
11 |
12 |
13 | class RainbowHighlighter(Highlighter):
14 | def highlight(self, text):
15 | for index in range(len(text)):
16 | text.stylize(f"color({randint(16, 255)})", index, index + 1)
17 |
18 |
19 | rainbow = RainbowHighlighter()
20 | print(rainbow("I must not fear. Fear is the mind-killer."))
21 |
--------------------------------------------------------------------------------
/examples/recursive_error.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | Demonstrates Rich tracebacks for recursion errors.
4 |
5 | Rich can exclude frames in the middle to avoid huge tracebacks.
6 |
7 | """
8 |
9 | from rich.console import Console
10 |
11 |
12 | def foo(n):
13 | return bar(n)
14 |
15 |
16 | def bar(n):
17 | return foo(n)
18 |
19 |
20 | console = Console()
21 |
22 | try:
23 | foo(1)
24 | except Exception:
25 | console.print_exception(max_frames=20)
26 |
--------------------------------------------------------------------------------
/examples/repr.py:
--------------------------------------------------------------------------------
1 | import rich.repr
2 |
3 |
4 | @rich.repr.auto
5 | class Bird:
6 | def __init__(self, name, eats=None, fly=True, extinct=False):
7 | self.name = name
8 | self.eats = list(eats) if eats else []
9 | self.fly = fly
10 | self.extinct = extinct
11 |
12 |
13 | # Note that the repr is still generated without Rich
14 | # Try commenting out the following line
15 |
16 | from rich import print
17 |
18 | BIRDS = {
19 | "gull": Bird("gull", eats=["fish", "chips", "ice cream", "sausage rolls"]),
20 | "penguin": Bird("penguin", eats=["fish"], fly=False),
21 | "dodo": Bird("dodo", eats=["fruit"], fly=False, extinct=True),
22 | }
23 | print(BIRDS)
24 |
--------------------------------------------------------------------------------
/examples/save_table_svg.py:
--------------------------------------------------------------------------------
1 | """
2 | Demonstrates how to export a SVG
3 | """
4 |
5 | from rich.console import Console
6 | from rich.table import Table
7 |
8 | table = Table(title="Star Wars Movies")
9 |
10 | table.add_column("Released", style="cyan", no_wrap=True)
11 | table.add_column("Title", style="magenta")
12 | table.add_column("Box Office", justify="right", style="green")
13 |
14 | table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690")
15 | table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
16 | table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889")
17 | table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889")
18 |
19 | console = Console(record=True)
20 | console.print(table, justify="center")
21 | console.save_svg("table.svg", title="save_table_svg.py")
22 |
23 | import os
24 | import webbrowser
25 |
26 | webbrowser.open(f"file://{os.path.abspath('table.svg')}")
27 |
--------------------------------------------------------------------------------
/examples/screen.py:
--------------------------------------------------------------------------------
1 | """
2 | Demonstration of Console.screen()
3 | """
4 |
5 | from time import sleep
6 |
7 | from rich.align import Align
8 | from rich.console import Console
9 | from rich.panel import Panel
10 |
11 | console = Console()
12 |
13 | with console.screen(style="bold white on red") as screen:
14 | text = Align.center("[blink]Don't Panic![/blink]", vertical="middle")
15 | screen.update(Panel(text))
16 | sleep(5)
17 |
--------------------------------------------------------------------------------
/examples/spinners.py:
--------------------------------------------------------------------------------
1 | from time import sleep
2 |
3 | from rich.columns import Columns
4 | from rich.panel import Panel
5 | from rich.live import Live
6 | from rich.text import Text
7 | from rich.spinner import Spinner, SPINNERS
8 |
9 | all_spinners = Columns(
10 | [
11 | Spinner(spinner_name, text=Text(repr(spinner_name), style="green"))
12 | for spinner_name in sorted(SPINNERS)
13 | ],
14 | column_first=True,
15 | expand=True,
16 | )
17 |
18 | with Live(
19 | Panel(all_spinners, title="Spinners", border_style="blue"),
20 | refresh_per_second=20,
21 | ) as live:
22 | while True:
23 | sleep(0.1)
24 |
--------------------------------------------------------------------------------
/examples/status.py:
--------------------------------------------------------------------------------
1 | from time import sleep
2 | from rich.console import Console
3 |
4 | console = Console()
5 | console.print()
6 |
7 | tasks = [f"task {n}" for n in range(1, 11)]
8 |
9 | with console.status("[bold green]Working on tasks...") as status:
10 | while tasks:
11 | task = tasks.pop(0)
12 | sleep(1)
13 | console.log(f"{task} complete")
14 |
--------------------------------------------------------------------------------
/examples/suppress.py:
--------------------------------------------------------------------------------
1 | try:
2 | import click
3 | except ImportError:
4 | print("Please install click for this example")
5 | print(" pip install click")
6 | exit()
7 |
8 | from rich.traceback import install
9 |
10 | install(suppress=[click])
11 |
12 |
13 | @click.command()
14 | @click.option("--count", default=1, help="Number of greetings.")
15 | def hello(count):
16 | """Simple program that greets NAME for a total of COUNT times."""
17 | 1 / 0
18 | for x in range(count):
19 | click.echo(f"Hello {name}!")
20 |
21 |
22 | if __name__ == "__main__":
23 | hello()
24 |
--------------------------------------------------------------------------------
/examples/table.py:
--------------------------------------------------------------------------------
1 | """
2 | Demonstrates how to render a table.
3 | """
4 |
5 | from rich.console import Console
6 | from rich.table import Table
7 |
8 | table = Table(title="Star Wars Movies")
9 |
10 | table.add_column("Released", style="cyan", no_wrap=True)
11 | table.add_column("Title", style="magenta")
12 | table.add_column("Box Office", justify="right", style="green")
13 |
14 | table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690")
15 | table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
16 | table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889")
17 | table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889")
18 |
19 | console = Console()
20 | console.print(table, justify="center")
21 |
--------------------------------------------------------------------------------
/examples/top_lite_simulator.py:
--------------------------------------------------------------------------------
1 | """Lite simulation of the top linux command."""
2 | import datetime
3 | import random
4 | import sys
5 | import time
6 | from dataclasses import dataclass
7 |
8 | from rich import box
9 | from rich.console import Console
10 | from rich.live import Live
11 | from rich.table import Table
12 |
13 | if sys.version_info >= (3, 8):
14 | from typing import Literal
15 | else:
16 | from typing_extensions import Literal
17 |
18 |
19 | @dataclass
20 | class Process:
21 | pid: int
22 | command: str
23 | cpu_percent: float
24 | memory: int
25 | start_time: datetime.datetime
26 | thread_count: int
27 | state: Literal["running", "sleeping"]
28 |
29 | @property
30 | def memory_str(self) -> str:
31 | if self.memory > 1e6:
32 | return f"{int(self.memory/1e6)}M"
33 | if self.memory > 1e3:
34 | return f"{int(self.memory/1e3)}K"
35 | return str(self.memory)
36 |
37 | @property
38 | def time_str(self) -> str:
39 | return str(datetime.datetime.now() - self.start_time)
40 |
41 |
42 | def generate_process(pid: int) -> Process:
43 | return Process(
44 | pid=pid,
45 | command=f"Process {pid}",
46 | cpu_percent=random.random() * 20,
47 | memory=random.randint(10, 200) ** 3,
48 | start_time=datetime.datetime.now()
49 | - datetime.timedelta(seconds=random.randint(0, 500) ** 2),
50 | thread_count=random.randint(1, 32),
51 | state="running" if random.randint(0, 10) < 8 else "sleeping",
52 | )
53 |
54 |
55 | def create_process_table(height: int) -> Table:
56 | processes = sorted(
57 | [generate_process(pid) for pid in range(height)],
58 | key=lambda p: p.cpu_percent,
59 | reverse=True,
60 | )
61 | table = Table(
62 | "PID", "Command", "CPU %", "Memory", "Time", "Thread #", "State", box=box.SIMPLE
63 | )
64 |
65 | for process in processes:
66 | table.add_row(
67 | str(process.pid),
68 | process.command,
69 | f"{process.cpu_percent:.1f}",
70 | process.memory_str,
71 | process.time_str,
72 | str(process.thread_count),
73 | process.state,
74 | )
75 |
76 | return table
77 |
78 |
79 | console = Console()
80 |
81 | with Live(console=console, screen=True, auto_refresh=False) as live:
82 | while True:
83 | live.update(create_process_table(console.size.height - 4), refresh=True)
84 | time.sleep(1)
85 |
--------------------------------------------------------------------------------
/examples/tree.py:
--------------------------------------------------------------------------------
1 | """
2 | Demonstrates how to display a tree of files / directories with the Tree renderable.
3 | """
4 |
5 | import os
6 | import pathlib
7 | import sys
8 |
9 | from rich import print
10 | from rich.filesize import decimal
11 | from rich.markup import escape
12 | from rich.text import Text
13 | from rich.tree import Tree
14 |
15 |
16 | def walk_directory(directory: pathlib.Path, tree: Tree) -> None:
17 | """Recursively build a Tree with directory contents."""
18 | # Sort dirs first then by filename
19 | paths = sorted(
20 | pathlib.Path(directory).iterdir(),
21 | key=lambda path: (path.is_file(), path.name.lower()),
22 | )
23 | for path in paths:
24 | # Remove hidden files
25 | if path.name.startswith("."):
26 | continue
27 | if path.is_dir():
28 | style = "dim" if path.name.startswith("__") else ""
29 | branch = tree.add(
30 | f"[bold magenta]:open_file_folder: [link file://{path}]{escape(path.name)}",
31 | style=style,
32 | guide_style=style,
33 | )
34 | walk_directory(path, branch)
35 | else:
36 | text_filename = Text(path.name, "green")
37 | text_filename.highlight_regex(r"\..*$", "bold red")
38 | text_filename.stylize(f"link file://{path}")
39 | file_size = path.stat().st_size
40 | text_filename.append(f" ({decimal(file_size)})", "blue")
41 | icon = "🐍 " if path.suffix == ".py" else "📄 "
42 | tree.add(Text(icon) + text_filename)
43 |
44 |
45 | try:
46 | directory = os.path.abspath(sys.argv[1])
47 | except IndexError:
48 | print("[b]Usage:[/] python tree.py ")
49 | else:
50 | tree = Tree(
51 | f":open_file_folder: [link file://{directory}]{directory}",
52 | guide_style="bold bright_blue",
53 | )
54 | walk_directory(pathlib.Path(directory), tree)
55 | print(tree)
56 |
--------------------------------------------------------------------------------
/faq.yml:
--------------------------------------------------------------------------------
1 | # FAQtory settings
2 |
3 | faq_url: "https://github.com/textualize/rich/blob/master/FAQ.md" # Replace this with the URL to your FAQ.md!
4 |
5 | questions_path: "./questions" # Where questions should be stored
6 | output_path: "./FAQ.md" # Where FAQ.md should be generated
7 | templates_path: ".faq" # Path to templates
8 |
--------------------------------------------------------------------------------
/imgs/columns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/columns.png
--------------------------------------------------------------------------------
/imgs/downloader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/downloader.gif
--------------------------------------------------------------------------------
/imgs/features.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/features.png
--------------------------------------------------------------------------------
/imgs/hello_world.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/hello_world.png
--------------------------------------------------------------------------------
/imgs/inspect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/inspect.png
--------------------------------------------------------------------------------
/imgs/log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/log.png
--------------------------------------------------------------------------------
/imgs/logging.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/logging.png
--------------------------------------------------------------------------------
/imgs/markdown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/markdown.png
--------------------------------------------------------------------------------
/imgs/print.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/print.png
--------------------------------------------------------------------------------
/imgs/progress.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/progress.gif
--------------------------------------------------------------------------------
/imgs/progress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/progress.png
--------------------------------------------------------------------------------
/imgs/repl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/repl.png
--------------------------------------------------------------------------------
/imgs/spinners.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/spinners.gif
--------------------------------------------------------------------------------
/imgs/status.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/status.gif
--------------------------------------------------------------------------------
/imgs/syntax.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/syntax.png
--------------------------------------------------------------------------------
/imgs/table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/table.png
--------------------------------------------------------------------------------
/imgs/table2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/table2.png
--------------------------------------------------------------------------------
/imgs/table_movie.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/table_movie.gif
--------------------------------------------------------------------------------
/imgs/traceback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/traceback.png
--------------------------------------------------------------------------------
/imgs/tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/tree.png
--------------------------------------------------------------------------------
/imgs/where_there_is_a_will.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/imgs/where_there_is_a_will.png
--------------------------------------------------------------------------------
/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "rich"
3 | homepage = "https://github.com/Textualize/rich"
4 | documentation = "https://rich.readthedocs.io/en/latest/"
5 | version = "14.0.0"
6 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
7 | authors = ["Will McGugan "]
8 | license = "MIT"
9 | readme = "README.md"
10 | classifiers = [
11 | "Development Status :: 5 - Production/Stable",
12 | "Environment :: Console",
13 | "Framework :: IPython",
14 | "Intended Audience :: Developers",
15 | "Operating System :: Microsoft :: Windows",
16 | "Operating System :: MacOS",
17 | "Operating System :: POSIX :: Linux",
18 | "Programming Language :: Python :: 3.8",
19 | "Programming Language :: Python :: 3.9",
20 | "Programming Language :: Python :: 3.10",
21 | "Programming Language :: Python :: 3.11",
22 | "Programming Language :: Python :: 3.12",
23 | "Programming Language :: Python :: 3.13",
24 | "Typing :: Typed",
25 | ]
26 | include = ["rich/py.typed"]
27 |
28 |
29 | [tool.poetry.dependencies]
30 | python = ">=3.8.0"
31 | typing-extensions = { version = ">=4.0.0, <5.0", python = "<3.11" }
32 | pygments = "^2.13.0"
33 | ipywidgets = { version = ">=7.5.1,<9", optional = true }
34 | markdown-it-py = ">=2.2.0"
35 |
36 | [tool.poetry.extras]
37 | jupyter = ["ipywidgets"]
38 |
39 | [tool.poetry.dev-dependencies]
40 | pytest = "^7.0.0"
41 | black = "^22.6"
42 | mypy = "^1.11"
43 | pytest-cov = "^3.0.0"
44 | attrs = "^21.4.0"
45 | pre-commit = "^2.17.0"
46 | asv = "^0.5.1"
47 |
48 | [build-system]
49 | requires = ["poetry-core>=1.0.0"]
50 | build-backend = "poetry.core.masonry.api"
51 |
52 |
53 | [tool.mypy]
54 | files = ["rich"]
55 | show_error_codes = true
56 | strict = true
57 | enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
58 |
59 |
60 | [[tool.mypy.overrides]]
61 | module = ["pygments.*", "IPython.*", "ipywidgets.*"]
62 | ignore_missing_imports = true
63 |
64 |
65 | [tool.pytest.ini_options]
66 | testpaths = ["tests"]
67 |
68 | [tool.isort]
69 | profile = "black"
70 |
--------------------------------------------------------------------------------
/questions/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Questions
3 |
4 | Your questions should go in this directory.
5 |
6 | Question files should be named with the extension ".question.md".
7 |
--------------------------------------------------------------------------------
/questions/ansi_escapes.question.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Natively inserted ANSI escape sequence characters break alignment of Panel."
3 | alt_titles:
4 | - "Escape codes break alignment."
5 | ---
6 |
7 | If you print ansi escape sequences for color and style you may find the output breaks your output.
8 | You may find that border characters in Panel and Table are in the wrong place, for example.
9 |
10 | As a general rule, you should allow Rich to generate all ansi escape sequences, so it can correctly account for these invisible characters.
11 | If you can't avoid a string with escape codes, you can convert it to an equivalent `Text` instance with `Text.from_ansi`.
12 |
--------------------------------------------------------------------------------
/questions/emoji_broken.question.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Why does emoji break alignment in a Table or Panel?"
3 | ---
4 |
5 | Certain emoji take up double space within the terminal. Unfortunately, terminals don't always agree how wide a given character should be.
6 |
7 | Rich has no way of knowing how wide a character will be on any given terminal. This can break alignment in containers like Table and Panel, where Rich needs to know the width of the content.
8 |
9 | There are also *multiple codepoints* characters, such as country flags, and emoji modifiers, which produce wildly different results across terminal emulators.
10 |
11 | Fortunately, most characters will work just fine. But you may have to avoid using the emojis that break alignment. You will get good results if you stick to emoji released on or before version 9 of the Unicode database,
12 |
--------------------------------------------------------------------------------
/questions/highlighting_unexpected.question.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Strange colors in console output."
3 | alt_titles:
4 | - "Why are numbers in cyan?"
5 | - "Rich print seems to make decimals cyan"
6 | - "Numbers get printed differently"
7 | ---
8 |
9 | Rich will highlight certain patterns in your output such as numbers, strings, and other objects like IP addresses.
10 |
11 | Occasionally this may also highlight parts of your output you didn't intend. See the [docs on highlighting](https://rich.readthedocs.io/en/latest/highlighting.html) for how to disable highlighting.
12 |
--------------------------------------------------------------------------------
/questions/log_renderables.question.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "How do I log a renderable?"
3 | alt_titles:
4 | - "Cannot log Tree() output to file"
5 | - "Log a Panel or Table to a RichHandler"
6 | ---
7 |
8 | Python's logging module is designed to work with strings. Consequently you won't be able to log Rich renderables (Table, Tree, etc) by calling `logger.debug` or other similar method.
9 |
10 | You could use the [capture](https://rich.readthedocs.io/en/latest/console.html#capturing-output) API to convert the renderable to a string and log that. However I would advise against it.
11 |
12 | Logging supports configurable back-ends, which means that a log message could go somewhere other than the terminal -- which may not correctly render the formatting and style produced by Rich.
13 |
14 | If you are only logging with a file-handler to stdout, then you probably don't need to use the logging module at all. Consider using [Console.log](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console.log) which will render anything that you can print with Rich, with a timestamp.
15 |
--------------------------------------------------------------------------------
/questions/logging_color.question.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "How do I render console markup in RichHandler?"
3 | alt_titles:
4 | - "Color is not applied during logging."
5 | - "Style tags don't work with logging handler"
6 |
7 | ---
8 |
9 | Console markup won't work anywhere else, other than `RichHandler` -- which is why they are disabled by default.
10 |
11 | See the docs if you want to [enable console markup](https://rich.readthedocs.io/en/latest/logging.html#logging-handler) in the logging handler.
12 |
--------------------------------------------------------------------------------
/questions/rich_spinner.question.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "python -m rich.spinner shows extra lines."
3 | ---
4 |
5 | The spinner example is know to break on some terminals (Windows in particular).
6 |
7 | Some terminals don't display emoji with the correct width, which means Rich can't always align them accurately inside a panel.
8 |
--------------------------------------------------------------------------------
/questions/square_brackets.question.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Why does content in square brackets disappear?"
3 | alt_titles:
4 | - "Can not print a [string]"
5 | ---
6 |
7 | Rich will treat text within square brackets as *markup tags*, for instance `"[bold]This is bold[/bold]"`.
8 |
9 | If you are printing strings with literally square brackets you can either disable markup, or escape your strings.
10 | See the docs on [console markup](https://rich.readthedocs.io/en/latest/markup.html) for how to do this.
11 |
--------------------------------------------------------------------------------
/questions/tracebacks_installed.question.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Rich is automatically installing traceback handler."
3 | alt_titles:
4 | - "Can you stop overriding traceback message formatting by default?"
5 | ---
6 |
7 | Rich will never install the traceback handler automatically.
8 |
9 | If you are getting Rich tracebacks and you don't want them, then some other piece of software is calling `rich.traceback.install()`.
10 |
--------------------------------------------------------------------------------
/rich/_emoji_replace.py:
--------------------------------------------------------------------------------
1 | from typing import Callable, Match, Optional
2 | import re
3 |
4 | from ._emoji_codes import EMOJI
5 |
6 |
7 | _ReStringMatch = Match[str] # regex match object
8 | _ReSubCallable = Callable[[_ReStringMatch], str] # Callable invoked by re.sub
9 | _EmojiSubMethod = Callable[[_ReSubCallable, str], str] # Sub method of a compiled re
10 |
11 |
12 | def _emoji_replace(
13 | text: str,
14 | default_variant: Optional[str] = None,
15 | _emoji_sub: _EmojiSubMethod = re.compile(r"(:(\S*?)(?:(?:\-)(emoji|text))?:)").sub,
16 | ) -> str:
17 | """Replace emoji code in text."""
18 | get_emoji = EMOJI.__getitem__
19 | variants = {"text": "\uFE0E", "emoji": "\uFE0F"}
20 | get_variant = variants.get
21 | default_variant_code = variants.get(default_variant, "") if default_variant else ""
22 |
23 | def do_replace(match: Match[str]) -> str:
24 | emoji_code, emoji_name, variant = match.groups()
25 | try:
26 | return get_emoji(emoji_name.lower()) + get_variant(
27 | variant, default_variant_code
28 | )
29 | except KeyError:
30 | return emoji_code
31 |
32 | return _emoji_sub(do_replace, text)
33 |
--------------------------------------------------------------------------------
/rich/_export_format.py:
--------------------------------------------------------------------------------
1 | CONSOLE_HTML_FORMAT = """\
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 | {code}
16 |
17 |
18 | """
19 |
20 | CONSOLE_SVG_FORMAT = """\
21 |
73 | """
74 |
75 | _SVG_FONT_FAMILY = "Rich Fira Code"
76 | _SVG_CLASSES_PREFIX = "rich-svg"
77 |
--------------------------------------------------------------------------------
/rich/_extension.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 |
4 | def load_ipython_extension(ip: Any) -> None: # pragma: no cover
5 | # prevent circular import
6 | from rich.pretty import install
7 | from rich.traceback import install as tr_install
8 |
9 | install()
10 | tr_install()
11 |
--------------------------------------------------------------------------------
/rich/_fileno.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import IO, Callable
4 |
5 |
6 | def get_fileno(file_like: IO[str]) -> int | None:
7 | """Get fileno() from a file, accounting for poorly implemented file-like objects.
8 |
9 | Args:
10 | file_like (IO): A file-like object.
11 |
12 | Returns:
13 | int | None: The result of fileno if available, or None if operation failed.
14 | """
15 | fileno: Callable[[], int] | None = getattr(file_like, "fileno", None)
16 | if fileno is not None:
17 | try:
18 | return fileno()
19 | except Exception:
20 | # `fileno` is documented as potentially raising a OSError
21 | # Alas, from the issues, there are so many poorly implemented file-like objects,
22 | # that `fileno()` can raise just about anything.
23 | return None
24 | return None
25 |
--------------------------------------------------------------------------------
/rich/_log_render.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from typing import Iterable, List, Optional, TYPE_CHECKING, Union, Callable
3 |
4 |
5 | from .text import Text, TextType
6 |
7 | if TYPE_CHECKING:
8 | from .console import Console, ConsoleRenderable, RenderableType
9 | from .table import Table
10 |
11 | FormatTimeCallable = Callable[[datetime], Text]
12 |
13 |
14 | class LogRender:
15 | def __init__(
16 | self,
17 | show_time: bool = True,
18 | show_level: bool = False,
19 | show_path: bool = True,
20 | time_format: Union[str, FormatTimeCallable] = "[%x %X]",
21 | omit_repeated_times: bool = True,
22 | level_width: Optional[int] = 8,
23 | ) -> None:
24 | self.show_time = show_time
25 | self.show_level = show_level
26 | self.show_path = show_path
27 | self.time_format = time_format
28 | self.omit_repeated_times = omit_repeated_times
29 | self.level_width = level_width
30 | self._last_time: Optional[Text] = None
31 |
32 | def __call__(
33 | self,
34 | console: "Console",
35 | renderables: Iterable["ConsoleRenderable"],
36 | log_time: Optional[datetime] = None,
37 | time_format: Optional[Union[str, FormatTimeCallable]] = None,
38 | level: TextType = "",
39 | path: Optional[str] = None,
40 | line_no: Optional[int] = None,
41 | link_path: Optional[str] = None,
42 | ) -> "Table":
43 | from .containers import Renderables
44 | from .table import Table
45 |
46 | output = Table.grid(padding=(0, 1))
47 | output.expand = True
48 | if self.show_time:
49 | output.add_column(style="log.time")
50 | if self.show_level:
51 | output.add_column(style="log.level", width=self.level_width)
52 | output.add_column(ratio=1, style="log.message", overflow="fold")
53 | if self.show_path and path:
54 | output.add_column(style="log.path")
55 | row: List["RenderableType"] = []
56 | if self.show_time:
57 | log_time = log_time or console.get_datetime()
58 | time_format = time_format or self.time_format
59 | if callable(time_format):
60 | log_time_display = time_format(log_time)
61 | else:
62 | log_time_display = Text(log_time.strftime(time_format))
63 | if log_time_display == self._last_time and self.omit_repeated_times:
64 | row.append(Text(" " * len(log_time_display)))
65 | else:
66 | row.append(log_time_display)
67 | self._last_time = log_time_display
68 | if self.show_level:
69 | row.append(level)
70 |
71 | row.append(Renderables(renderables))
72 | if self.show_path and path:
73 | path_text = Text()
74 | path_text.append(
75 | path, style=f"link file://{link_path}" if link_path else ""
76 | )
77 | if line_no:
78 | path_text.append(":")
79 | path_text.append(
80 | f"{line_no}",
81 | style=f"link file://{link_path}#{line_no}" if link_path else "",
82 | )
83 | row.append(path_text)
84 |
85 | output.add_row(*row)
86 | return output
87 |
88 |
89 | if __name__ == "__main__": # pragma: no cover
90 | from rich.console import Console
91 |
92 | c = Console()
93 | c.print("[on blue]Hello", justify="right")
94 | c.log("[on blue]hello", justify="right")
95 |
--------------------------------------------------------------------------------
/rich/_loop.py:
--------------------------------------------------------------------------------
1 | from typing import Iterable, Tuple, TypeVar
2 |
3 | T = TypeVar("T")
4 |
5 |
6 | def loop_first(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
7 | """Iterate and generate a tuple with a flag for first value."""
8 | iter_values = iter(values)
9 | try:
10 | value = next(iter_values)
11 | except StopIteration:
12 | return
13 | yield True, value
14 | for value in iter_values:
15 | yield False, value
16 |
17 |
18 | def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
19 | """Iterate and generate a tuple with a flag for last value."""
20 | iter_values = iter(values)
21 | try:
22 | previous_value = next(iter_values)
23 | except StopIteration:
24 | return
25 | for value in iter_values:
26 | yield False, previous_value
27 | previous_value = value
28 | yield True, previous_value
29 |
30 |
31 | def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]:
32 | """Iterate and generate a tuple with a flag for first and last value."""
33 | iter_values = iter(values)
34 | try:
35 | previous_value = next(iter_values)
36 | except StopIteration:
37 | return
38 | first = True
39 | for value in iter_values:
40 | yield first, False, previous_value
41 | first = False
42 | previous_value = value
43 | yield first, True, previous_value
44 |
--------------------------------------------------------------------------------
/rich/_null_file.py:
--------------------------------------------------------------------------------
1 | from types import TracebackType
2 | from typing import IO, Iterable, Iterator, List, Optional, Type
3 |
4 |
5 | class NullFile(IO[str]):
6 | def close(self) -> None:
7 | pass
8 |
9 | def isatty(self) -> bool:
10 | return False
11 |
12 | def read(self, __n: int = 1) -> str:
13 | return ""
14 |
15 | def readable(self) -> bool:
16 | return False
17 |
18 | def readline(self, __limit: int = 1) -> str:
19 | return ""
20 |
21 | def readlines(self, __hint: int = 1) -> List[str]:
22 | return []
23 |
24 | def seek(self, __offset: int, __whence: int = 1) -> int:
25 | return 0
26 |
27 | def seekable(self) -> bool:
28 | return False
29 |
30 | def tell(self) -> int:
31 | return 0
32 |
33 | def truncate(self, __size: Optional[int] = 1) -> int:
34 | return 0
35 |
36 | def writable(self) -> bool:
37 | return False
38 |
39 | def writelines(self, __lines: Iterable[str]) -> None:
40 | pass
41 |
42 | def __next__(self) -> str:
43 | return ""
44 |
45 | def __iter__(self) -> Iterator[str]:
46 | return iter([""])
47 |
48 | def __enter__(self) -> IO[str]:
49 | return self
50 |
51 | def __exit__(
52 | self,
53 | __t: Optional[Type[BaseException]],
54 | __value: Optional[BaseException],
55 | __traceback: Optional[TracebackType],
56 | ) -> None:
57 | pass
58 |
59 | def write(self, text: str) -> int:
60 | return 0
61 |
62 | def flush(self) -> None:
63 | pass
64 |
65 | def fileno(self) -> int:
66 | return -1
67 |
68 |
69 | NULL_FILE = NullFile()
70 |
--------------------------------------------------------------------------------
/rich/_pick.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 |
4 | def pick_bool(*values: Optional[bool]) -> bool:
5 | """Pick the first non-none bool or return the last value.
6 |
7 | Args:
8 | *values (bool): Any number of boolean or None values.
9 |
10 | Returns:
11 | bool: First non-none boolean.
12 | """
13 | assert values, "1 or more values required"
14 | for value in values:
15 | if value is not None:
16 | return value
17 | return bool(value)
18 |
--------------------------------------------------------------------------------
/rich/_stack.py:
--------------------------------------------------------------------------------
1 | from typing import List, TypeVar
2 |
3 | T = TypeVar("T")
4 |
5 |
6 | class Stack(List[T]):
7 | """A small shim over builtin list."""
8 |
9 | @property
10 | def top(self) -> T:
11 | """Get top of stack."""
12 | return self[-1]
13 |
14 | def push(self, item: T) -> None:
15 | """Push an item on to the stack (append in stack nomenclature)."""
16 | self.append(item)
17 |
--------------------------------------------------------------------------------
/rich/_timer.py:
--------------------------------------------------------------------------------
1 | """
2 | Timer context manager, only used in debug.
3 |
4 | """
5 |
6 | from time import time
7 |
8 | import contextlib
9 | from typing import Generator
10 |
11 |
12 | @contextlib.contextmanager
13 | def timer(subject: str = "time") -> Generator[None, None, None]:
14 | """print the elapsed time. (only used in debugging)"""
15 | start = time()
16 | yield
17 | elapsed = time() - start
18 | elapsed_ms = elapsed * 1000
19 | print(f"{subject} elapsed {elapsed_ms:.1f}ms")
20 |
--------------------------------------------------------------------------------
/rich/_windows.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from dataclasses import dataclass
3 |
4 |
5 | @dataclass
6 | class WindowsConsoleFeatures:
7 | """Windows features available."""
8 |
9 | vt: bool = False
10 | """The console supports VT codes."""
11 | truecolor: bool = False
12 | """The console supports truecolor."""
13 |
14 |
15 | try:
16 | import ctypes
17 | from ctypes import LibraryLoader
18 |
19 | if sys.platform == "win32":
20 | windll = LibraryLoader(ctypes.WinDLL)
21 | else:
22 | windll = None
23 | raise ImportError("Not windows")
24 |
25 | from rich._win32_console import (
26 | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
27 | GetConsoleMode,
28 | GetStdHandle,
29 | LegacyWindowsError,
30 | )
31 |
32 | except (AttributeError, ImportError, ValueError):
33 | # Fallback if we can't load the Windows DLL
34 | def get_windows_console_features() -> WindowsConsoleFeatures:
35 | features = WindowsConsoleFeatures()
36 | return features
37 |
38 | else:
39 |
40 | def get_windows_console_features() -> WindowsConsoleFeatures:
41 | """Get windows console features.
42 |
43 | Returns:
44 | WindowsConsoleFeatures: An instance of WindowsConsoleFeatures.
45 | """
46 | handle = GetStdHandle()
47 | try:
48 | console_mode = GetConsoleMode(handle)
49 | success = True
50 | except LegacyWindowsError:
51 | console_mode = 0
52 | success = False
53 | vt = bool(success and console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
54 | truecolor = False
55 | if vt:
56 | win_version = sys.getwindowsversion()
57 | truecolor = win_version.major > 10 or (
58 | win_version.major == 10 and win_version.build >= 15063
59 | )
60 | features = WindowsConsoleFeatures(vt=vt, truecolor=truecolor)
61 | return features
62 |
63 |
64 | if __name__ == "__main__":
65 | import platform
66 |
67 | features = get_windows_console_features()
68 | from rich import print
69 |
70 | print(f'platform="{platform.system()}"')
71 | print(repr(features))
72 |
--------------------------------------------------------------------------------
/rich/_windows_renderer.py:
--------------------------------------------------------------------------------
1 | from typing import Iterable, Sequence, Tuple, cast
2 |
3 | from rich._win32_console import LegacyWindowsTerm, WindowsCoordinates
4 | from rich.segment import ControlCode, ControlType, Segment
5 |
6 |
7 | def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) -> None:
8 | """Makes appropriate Windows Console API calls based on the segments in the buffer.
9 |
10 | Args:
11 | buffer (Iterable[Segment]): Iterable of Segments to convert to Win32 API calls.
12 | term (LegacyWindowsTerm): Used to call the Windows Console API.
13 | """
14 | for text, style, control in buffer:
15 | if not control:
16 | if style:
17 | term.write_styled(text, style)
18 | else:
19 | term.write_text(text)
20 | else:
21 | control_codes: Sequence[ControlCode] = control
22 | for control_code in control_codes:
23 | control_type = control_code[0]
24 | if control_type == ControlType.CURSOR_MOVE_TO:
25 | _, x, y = cast(Tuple[ControlType, int, int], control_code)
26 | term.move_cursor_to(WindowsCoordinates(row=y - 1, col=x - 1))
27 | elif control_type == ControlType.CARRIAGE_RETURN:
28 | term.write_text("\r")
29 | elif control_type == ControlType.HOME:
30 | term.move_cursor_to(WindowsCoordinates(0, 0))
31 | elif control_type == ControlType.CURSOR_UP:
32 | term.move_cursor_up()
33 | elif control_type == ControlType.CURSOR_DOWN:
34 | term.move_cursor_down()
35 | elif control_type == ControlType.CURSOR_FORWARD:
36 | term.move_cursor_forward()
37 | elif control_type == ControlType.CURSOR_BACKWARD:
38 | term.move_cursor_backward()
39 | elif control_type == ControlType.CURSOR_MOVE_TO_COLUMN:
40 | _, column = cast(Tuple[ControlType, int], control_code)
41 | term.move_cursor_to_column(column - 1)
42 | elif control_type == ControlType.HIDE_CURSOR:
43 | term.hide_cursor()
44 | elif control_type == ControlType.SHOW_CURSOR:
45 | term.show_cursor()
46 | elif control_type == ControlType.ERASE_IN_LINE:
47 | _, mode = cast(Tuple[ControlType, int], control_code)
48 | if mode == 0:
49 | term.erase_end_of_line()
50 | elif mode == 1:
51 | term.erase_start_of_line()
52 | elif mode == 2:
53 | term.erase_line()
54 | elif control_type == ControlType.SET_WINDOW_TITLE:
55 | _, title = cast(Tuple[ControlType, str], control_code)
56 | term.set_title(title)
57 |
--------------------------------------------------------------------------------
/rich/abc.py:
--------------------------------------------------------------------------------
1 | from abc import ABC
2 |
3 |
4 | class RichRenderable(ABC):
5 | """An abstract base class for Rich renderables.
6 |
7 | Note that there is no need to extend this class, the intended use is to check if an
8 | object supports the Rich renderable protocol. For example::
9 |
10 | if isinstance(my_object, RichRenderable):
11 | console.print(my_object)
12 |
13 | """
14 |
15 | @classmethod
16 | def __subclasshook__(cls, other: type) -> bool:
17 | """Check if this class supports the rich render protocol."""
18 | return hasattr(other, "__rich_console__") or hasattr(other, "__rich__")
19 |
20 |
21 | if __name__ == "__main__": # pragma: no cover
22 | from rich.text import Text
23 |
24 | t = Text()
25 | print(isinstance(Text, RichRenderable))
26 | print(isinstance(t, RichRenderable))
27 |
28 | class Foo:
29 | pass
30 |
31 | f = Foo()
32 | print(isinstance(f, RichRenderable))
33 | print(isinstance("", RichRenderable))
34 |
--------------------------------------------------------------------------------
/rich/bar.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, Union
2 |
3 | from .color import Color
4 | from .console import Console, ConsoleOptions, RenderResult
5 | from .jupyter import JupyterMixin
6 | from .measure import Measurement
7 | from .segment import Segment
8 | from .style import Style
9 |
10 | # There are left-aligned characters for 1/8 to 7/8, but
11 | # the right-aligned characters exist only for 1/8 and 4/8.
12 | BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"]
13 | END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
14 | FULL_BLOCK = "█"
15 |
16 |
17 | class Bar(JupyterMixin):
18 | """Renders a solid block bar.
19 |
20 | Args:
21 | size (float): Value for the end of the bar.
22 | begin (float): Begin point (between 0 and size, inclusive).
23 | end (float): End point (between 0 and size, inclusive).
24 | width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
25 | color (Union[Color, str], optional): Color of the bar. Defaults to "default".
26 | bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default".
27 | """
28 |
29 | def __init__(
30 | self,
31 | size: float,
32 | begin: float,
33 | end: float,
34 | *,
35 | width: Optional[int] = None,
36 | color: Union[Color, str] = "default",
37 | bgcolor: Union[Color, str] = "default",
38 | ):
39 | self.size = size
40 | self.begin = max(begin, 0)
41 | self.end = min(end, size)
42 | self.width = width
43 | self.style = Style(color=color, bgcolor=bgcolor)
44 |
45 | def __repr__(self) -> str:
46 | return f"Bar({self.size}, {self.begin}, {self.end})"
47 |
48 | def __rich_console__(
49 | self, console: Console, options: ConsoleOptions
50 | ) -> RenderResult:
51 | width = min(
52 | self.width if self.width is not None else options.max_width,
53 | options.max_width,
54 | )
55 |
56 | if self.begin >= self.end:
57 | yield Segment(" " * width, self.style)
58 | yield Segment.line()
59 | return
60 |
61 | prefix_complete_eights = int(width * 8 * self.begin / self.size)
62 | prefix_bar_count = prefix_complete_eights // 8
63 | prefix_eights_count = prefix_complete_eights % 8
64 |
65 | body_complete_eights = int(width * 8 * self.end / self.size)
66 | body_bar_count = body_complete_eights // 8
67 | body_eights_count = body_complete_eights % 8
68 |
69 | # When start and end fall into the same cell, we ideally should render
70 | # a symbol that's "center-aligned", but there is no good symbol in Unicode.
71 | # In this case, we fall back to right-aligned block symbol for simplicity.
72 |
73 | prefix = " " * prefix_bar_count
74 | if prefix_eights_count:
75 | prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count]
76 |
77 | body = FULL_BLOCK * body_bar_count
78 | if body_eights_count:
79 | body += END_BLOCK_ELEMENTS[body_eights_count]
80 |
81 | suffix = " " * (width - len(body))
82 |
83 | yield Segment(prefix + body[len(prefix) :] + suffix, self.style)
84 | yield Segment.line()
85 |
86 | def __rich_measure__(
87 | self, console: Console, options: ConsoleOptions
88 | ) -> Measurement:
89 | return (
90 | Measurement(self.width, self.width)
91 | if self.width is not None
92 | else Measurement(4, options.max_width)
93 | )
94 |
--------------------------------------------------------------------------------
/rich/color_triplet.py:
--------------------------------------------------------------------------------
1 | from typing import NamedTuple, Tuple
2 |
3 |
4 | class ColorTriplet(NamedTuple):
5 | """The red, green, and blue components of a color."""
6 |
7 | red: int
8 | """Red component in 0 to 255 range."""
9 | green: int
10 | """Green component in 0 to 255 range."""
11 | blue: int
12 | """Blue component in 0 to 255 range."""
13 |
14 | @property
15 | def hex(self) -> str:
16 | """get the color triplet in CSS style."""
17 | red, green, blue = self
18 | return f"#{red:02x}{green:02x}{blue:02x}"
19 |
20 | @property
21 | def rgb(self) -> str:
22 | """The color in RGB format.
23 |
24 | Returns:
25 | str: An rgb color, e.g. ``"rgb(100,23,255)"``.
26 | """
27 | red, green, blue = self
28 | return f"rgb({red},{green},{blue})"
29 |
30 | @property
31 | def normalized(self) -> Tuple[float, float, float]:
32 | """Convert components into floats between 0 and 1.
33 |
34 | Returns:
35 | Tuple[float, float, float]: A tuple of three normalized colour components.
36 | """
37 | red, green, blue = self
38 | return red / 255.0, green / 255.0, blue / 255.0
39 |
--------------------------------------------------------------------------------
/rich/constrain.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, TYPE_CHECKING
2 |
3 | from .jupyter import JupyterMixin
4 | from .measure import Measurement
5 |
6 | if TYPE_CHECKING:
7 | from .console import Console, ConsoleOptions, RenderableType, RenderResult
8 |
9 |
10 | class Constrain(JupyterMixin):
11 | """Constrain the width of a renderable to a given number of characters.
12 |
13 | Args:
14 | renderable (RenderableType): A renderable object.
15 | width (int, optional): The maximum width (in characters) to render. Defaults to 80.
16 | """
17 |
18 | def __init__(self, renderable: "RenderableType", width: Optional[int] = 80) -> None:
19 | self.renderable = renderable
20 | self.width = width
21 |
22 | def __rich_console__(
23 | self, console: "Console", options: "ConsoleOptions"
24 | ) -> "RenderResult":
25 | if self.width is None:
26 | yield self.renderable
27 | else:
28 | child_options = options.update_width(min(self.width, options.max_width))
29 | yield from console.render(self.renderable, child_options)
30 |
31 | def __rich_measure__(
32 | self, console: "Console", options: "ConsoleOptions"
33 | ) -> "Measurement":
34 | if self.width is not None:
35 | options = options.update_width(self.width)
36 | measurement = Measurement.get(console, options, self.renderable)
37 | return measurement
38 |
--------------------------------------------------------------------------------
/rich/diagnose.py:
--------------------------------------------------------------------------------
1 | import os
2 | import platform
3 |
4 | from rich import inspect
5 | from rich.console import Console, get_windows_console_features
6 | from rich.panel import Panel
7 | from rich.pretty import Pretty
8 |
9 |
10 | def report() -> None: # pragma: no cover
11 | """Print a report to the terminal with debugging information"""
12 | console = Console()
13 | inspect(console)
14 | features = get_windows_console_features()
15 | inspect(features)
16 |
17 | env_names = (
18 | "CLICOLOR",
19 | "COLORTERM",
20 | "COLUMNS",
21 | "JPY_PARENT_PID",
22 | "JUPYTER_COLUMNS",
23 | "JUPYTER_LINES",
24 | "LINES",
25 | "NO_COLOR",
26 | "TERM_PROGRAM",
27 | "TERM",
28 | "TTY_COMPATIBLE",
29 | "VSCODE_VERBOSE_LOGGING",
30 | )
31 | env = {name: os.getenv(name) for name in env_names}
32 | console.print(Panel.fit((Pretty(env)), title="[b]Environment Variables"))
33 |
34 | console.print(f'platform="{platform.system()}"')
35 |
36 |
37 | if __name__ == "__main__": # pragma: no cover
38 | report()
39 |
--------------------------------------------------------------------------------
/rich/emoji.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from typing import TYPE_CHECKING, Optional, Union
3 |
4 | from .jupyter import JupyterMixin
5 | from .segment import Segment
6 | from .style import Style
7 | from ._emoji_codes import EMOJI
8 | from ._emoji_replace import _emoji_replace
9 |
10 | if sys.version_info >= (3, 8):
11 | from typing import Literal
12 | else:
13 | from typing_extensions import Literal # pragma: no cover
14 |
15 |
16 | if TYPE_CHECKING:
17 | from .console import Console, ConsoleOptions, RenderResult
18 |
19 |
20 | EmojiVariant = Literal["emoji", "text"]
21 |
22 |
23 | class NoEmoji(Exception):
24 | """No emoji by that name."""
25 |
26 |
27 | class Emoji(JupyterMixin):
28 | __slots__ = ["name", "style", "_char", "variant"]
29 |
30 | VARIANTS = {"text": "\uFE0E", "emoji": "\uFE0F"}
31 |
32 | def __init__(
33 | self,
34 | name: str,
35 | style: Union[str, Style] = "none",
36 | variant: Optional[EmojiVariant] = None,
37 | ) -> None:
38 | """A single emoji character.
39 |
40 | Args:
41 | name (str): Name of emoji.
42 | style (Union[str, Style], optional): Optional style. Defaults to None.
43 |
44 | Raises:
45 | NoEmoji: If the emoji doesn't exist.
46 | """
47 | self.name = name
48 | self.style = style
49 | self.variant = variant
50 | try:
51 | self._char = EMOJI[name]
52 | except KeyError:
53 | raise NoEmoji(f"No emoji called {name!r}")
54 | if variant is not None:
55 | self._char += self.VARIANTS.get(variant, "")
56 |
57 | @classmethod
58 | def replace(cls, text: str) -> str:
59 | """Replace emoji markup with corresponding unicode characters.
60 |
61 | Args:
62 | text (str): A string with emojis codes, e.g. "Hello :smiley:!"
63 |
64 | Returns:
65 | str: A string with emoji codes replaces with actual emoji.
66 | """
67 | return _emoji_replace(text)
68 |
69 | def __repr__(self) -> str:
70 | return f""
71 |
72 | def __str__(self) -> str:
73 | return self._char
74 |
75 | def __rich_console__(
76 | self, console: "Console", options: "ConsoleOptions"
77 | ) -> "RenderResult":
78 | yield Segment(self._char, console.get_style(self.style))
79 |
80 |
81 | if __name__ == "__main__": # pragma: no cover
82 | import sys
83 |
84 | from rich.columns import Columns
85 | from rich.console import Console
86 |
87 | console = Console(record=True)
88 |
89 | columns = Columns(
90 | (f":{name}: {name}" for name in sorted(EMOJI.keys()) if "\u200D" not in name),
91 | column_first=True,
92 | )
93 |
94 | console.print(columns)
95 | if len(sys.argv) > 1:
96 | console.save_html(sys.argv[1])
97 |
--------------------------------------------------------------------------------
/rich/errors.py:
--------------------------------------------------------------------------------
1 | class ConsoleError(Exception):
2 | """An error in console operation."""
3 |
4 |
5 | class StyleError(Exception):
6 | """An error in styles."""
7 |
8 |
9 | class StyleSyntaxError(ConsoleError):
10 | """Style was badly formatted."""
11 |
12 |
13 | class MissingStyle(StyleError):
14 | """No such style."""
15 |
16 |
17 | class StyleStackError(ConsoleError):
18 | """Style stack is invalid."""
19 |
20 |
21 | class NotRenderableError(ConsoleError):
22 | """Object is not renderable."""
23 |
24 |
25 | class MarkupError(ConsoleError):
26 | """Markup was badly formatted."""
27 |
28 |
29 | class LiveError(ConsoleError):
30 | """Error related to Live display."""
31 |
32 |
33 | class NoAltScreen(ConsoleError):
34 | """Alt screen mode was required."""
35 |
--------------------------------------------------------------------------------
/rich/file_proxy.py:
--------------------------------------------------------------------------------
1 | import io
2 | from typing import IO, TYPE_CHECKING, Any, List
3 |
4 | from .ansi import AnsiDecoder
5 | from .text import Text
6 |
7 | if TYPE_CHECKING:
8 | from .console import Console
9 |
10 |
11 | class FileProxy(io.TextIOBase):
12 | """Wraps a file (e.g. sys.stdout) and redirects writes to a console."""
13 |
14 | def __init__(self, console: "Console", file: IO[str]) -> None:
15 | self.__console = console
16 | self.__file = file
17 | self.__buffer: List[str] = []
18 | self.__ansi_decoder = AnsiDecoder()
19 |
20 | @property
21 | def rich_proxied_file(self) -> IO[str]:
22 | """Get proxied file."""
23 | return self.__file
24 |
25 | def __getattr__(self, name: str) -> Any:
26 | return getattr(self.__file, name)
27 |
28 | def write(self, text: str) -> int:
29 | if not isinstance(text, str):
30 | raise TypeError(f"write() argument must be str, not {type(text).__name__}")
31 | buffer = self.__buffer
32 | lines: List[str] = []
33 | while text:
34 | line, new_line, text = text.partition("\n")
35 | if new_line:
36 | lines.append("".join(buffer) + line)
37 | buffer.clear()
38 | else:
39 | buffer.append(line)
40 | break
41 | if lines:
42 | console = self.__console
43 | with console:
44 | output = Text("\n").join(
45 | self.__ansi_decoder.decode_line(line) for line in lines
46 | )
47 | console.print(output)
48 | return len(text)
49 |
50 | def flush(self) -> None:
51 | output = "".join(self.__buffer)
52 | if output:
53 | self.__console.print(output)
54 | del self.__buffer[:]
55 |
56 | def fileno(self) -> int:
57 | return self.__file.fileno()
58 |
--------------------------------------------------------------------------------
/rich/filesize.py:
--------------------------------------------------------------------------------
1 | """Functions for reporting filesizes. Borrowed from https://github.com/PyFilesystem/pyfilesystem2
2 |
3 | The functions declared in this module should cover the different
4 | use cases needed to generate a string representation of a file size
5 | using several different units. Since there are many standards regarding
6 | file size units, three different functions have been implemented.
7 |
8 | See Also:
9 | * `Wikipedia: Binary prefix `_
10 |
11 | """
12 |
13 | __all__ = ["decimal"]
14 |
15 | from typing import Iterable, List, Optional, Tuple
16 |
17 |
18 | def _to_str(
19 | size: int,
20 | suffixes: Iterable[str],
21 | base: int,
22 | *,
23 | precision: Optional[int] = 1,
24 | separator: Optional[str] = " ",
25 | ) -> str:
26 | if size == 1:
27 | return "1 byte"
28 | elif size < base:
29 | return f"{size:,} bytes"
30 |
31 | for i, suffix in enumerate(suffixes, 2): # noqa: B007
32 | unit = base**i
33 | if size < unit:
34 | break
35 | return "{:,.{precision}f}{separator}{}".format(
36 | (base * size / unit),
37 | suffix,
38 | precision=precision,
39 | separator=separator,
40 | )
41 |
42 |
43 | def pick_unit_and_suffix(size: int, suffixes: List[str], base: int) -> Tuple[int, str]:
44 | """Pick a suffix and base for the given size."""
45 | for i, suffix in enumerate(suffixes):
46 | unit = base**i
47 | if size < unit * base:
48 | break
49 | return unit, suffix
50 |
51 |
52 | def decimal(
53 | size: int,
54 | *,
55 | precision: Optional[int] = 1,
56 | separator: Optional[str] = " ",
57 | ) -> str:
58 | """Convert a filesize in to a string (powers of 1000, SI prefixes).
59 |
60 | In this convention, ``1000 B = 1 kB``.
61 |
62 | This is typically the format used to advertise the storage
63 | capacity of USB flash drives and the like (*256 MB* meaning
64 | actually a storage capacity of more than *256 000 000 B*),
65 | or used by **Mac OS X** since v10.6 to report file sizes.
66 |
67 | Arguments:
68 | int (size): A file size.
69 | int (precision): The number of decimal places to include (default = 1).
70 | str (separator): The string to separate the value from the units (default = " ").
71 |
72 | Returns:
73 | `str`: A string containing a abbreviated file size and units.
74 |
75 | Example:
76 | >>> filesize.decimal(30000)
77 | '30.0 kB'
78 | >>> filesize.decimal(30000, precision=2, separator="")
79 | '30.00kB'
80 |
81 | """
82 | return _to_str(
83 | size,
84 | ("kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"),
85 | 1000,
86 | precision=precision,
87 | separator=separator,
88 | )
89 |
--------------------------------------------------------------------------------
/rich/pager.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from typing import Any
3 |
4 |
5 | class Pager(ABC):
6 | """Base class for a pager."""
7 |
8 | @abstractmethod
9 | def show(self, content: str) -> None:
10 | """Show content in pager.
11 |
12 | Args:
13 | content (str): Content to be displayed.
14 | """
15 |
16 |
17 | class SystemPager(Pager):
18 | """Uses the pager installed on the system."""
19 |
20 | def _pager(self, content: str) -> Any: # pragma: no cover
21 | return __import__("pydoc").pager(content)
22 |
23 | def show(self, content: str) -> None:
24 | """Use the same pager used by pydoc."""
25 | self._pager(content)
26 |
27 |
28 | if __name__ == "__main__": # pragma: no cover
29 | from .__main__ import make_test_card
30 | from .console import Console
31 |
32 | console = Console()
33 | with console.pager(styles=True):
34 | console.print(make_test_card())
35 |
--------------------------------------------------------------------------------
/rich/protocol.py:
--------------------------------------------------------------------------------
1 | from typing import Any, cast, Set, TYPE_CHECKING
2 | from inspect import isclass
3 |
4 | if TYPE_CHECKING:
5 | from rich.console import RenderableType
6 |
7 | _GIBBERISH = """aihwerij235234ljsdnp34ksodfipwoe234234jlskjdf"""
8 |
9 |
10 | def is_renderable(check_object: Any) -> bool:
11 | """Check if an object may be rendered by Rich."""
12 | return (
13 | isinstance(check_object, str)
14 | or hasattr(check_object, "__rich__")
15 | or hasattr(check_object, "__rich_console__")
16 | )
17 |
18 |
19 | def rich_cast(renderable: object) -> "RenderableType":
20 | """Cast an object to a renderable by calling __rich__ if present.
21 |
22 | Args:
23 | renderable (object): A potentially renderable object
24 |
25 | Returns:
26 | object: The result of recursively calling __rich__.
27 | """
28 | from rich.console import RenderableType
29 |
30 | rich_visited_set: Set[type] = set() # Prevent potential infinite loop
31 | while hasattr(renderable, "__rich__") and not isclass(renderable):
32 | # Detect object which claim to have all the attributes
33 | if hasattr(renderable, _GIBBERISH):
34 | return repr(renderable)
35 | cast_method = getattr(renderable, "__rich__")
36 | renderable = cast_method()
37 | renderable_type = type(renderable)
38 | if renderable_type in rich_visited_set:
39 | break
40 | rich_visited_set.add(renderable_type)
41 |
42 | return cast(RenderableType, renderable)
43 |
--------------------------------------------------------------------------------
/rich/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/rich/py.typed
--------------------------------------------------------------------------------
/rich/region.py:
--------------------------------------------------------------------------------
1 | from typing import NamedTuple
2 |
3 |
4 | class Region(NamedTuple):
5 | """Defines a rectangular region of the screen."""
6 |
7 | x: int
8 | y: int
9 | width: int
10 | height: int
11 |
--------------------------------------------------------------------------------
/rich/scope.py:
--------------------------------------------------------------------------------
1 | from collections.abc import Mapping
2 | from typing import TYPE_CHECKING, Any, Optional, Tuple
3 |
4 | from .highlighter import ReprHighlighter
5 | from .panel import Panel
6 | from .pretty import Pretty
7 | from .table import Table
8 | from .text import Text, TextType
9 |
10 | if TYPE_CHECKING:
11 | from .console import ConsoleRenderable
12 |
13 |
14 | def render_scope(
15 | scope: "Mapping[str, Any]",
16 | *,
17 | title: Optional[TextType] = None,
18 | sort_keys: bool = True,
19 | indent_guides: bool = False,
20 | max_length: Optional[int] = None,
21 | max_string: Optional[int] = None,
22 | ) -> "ConsoleRenderable":
23 | """Render python variables in a given scope.
24 |
25 | Args:
26 | scope (Mapping): A mapping containing variable names and values.
27 | title (str, optional): Optional title. Defaults to None.
28 | sort_keys (bool, optional): Enable sorting of items. Defaults to True.
29 | indent_guides (bool, optional): Enable indentation guides. Defaults to False.
30 | max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
31 | Defaults to None.
32 | max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
33 |
34 | Returns:
35 | ConsoleRenderable: A renderable object.
36 | """
37 | highlighter = ReprHighlighter()
38 | items_table = Table.grid(padding=(0, 1), expand=False)
39 | items_table.add_column(justify="right")
40 |
41 | def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]:
42 | """Sort special variables first, then alphabetically."""
43 | key, _ = item
44 | return (not key.startswith("__"), key.lower())
45 |
46 | items = sorted(scope.items(), key=sort_items) if sort_keys else scope.items()
47 | for key, value in items:
48 | key_text = Text.assemble(
49 | (key, "scope.key.special" if key.startswith("__") else "scope.key"),
50 | (" =", "scope.equals"),
51 | )
52 | items_table.add_row(
53 | key_text,
54 | Pretty(
55 | value,
56 | highlighter=highlighter,
57 | indent_guides=indent_guides,
58 | max_length=max_length,
59 | max_string=max_string,
60 | ),
61 | )
62 | return Panel.fit(
63 | items_table,
64 | title=title,
65 | border_style="scope.border",
66 | padding=(0, 1),
67 | )
68 |
69 |
70 | if __name__ == "__main__": # pragma: no cover
71 | from rich import print
72 |
73 | print()
74 |
75 | def test(foo: float, bar: float) -> None:
76 | list_of_things = [1, 2, 3, None, 4, True, False, "Hello World"]
77 | dict_of_things = {
78 | "version": "1.1",
79 | "method": "confirmFruitPurchase",
80 | "params": [["apple", "orange", "mangoes", "pomelo"], 1.123],
81 | "id": "194521489",
82 | }
83 | print(render_scope(locals(), title="[i]locals", sort_keys=False))
84 |
85 | test(20.3423, 3.1427)
86 | print()
87 |
--------------------------------------------------------------------------------
/rich/screen.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, TYPE_CHECKING
2 |
3 | from .segment import Segment
4 | from .style import StyleType
5 | from ._loop import loop_last
6 |
7 |
8 | if TYPE_CHECKING:
9 | from .console import (
10 | Console,
11 | ConsoleOptions,
12 | RenderResult,
13 | RenderableType,
14 | Group,
15 | )
16 |
17 |
18 | class Screen:
19 | """A renderable that fills the terminal screen and crops excess.
20 |
21 | Args:
22 | renderable (RenderableType): Child renderable.
23 | style (StyleType, optional): Optional background style. Defaults to None.
24 | """
25 |
26 | renderable: "RenderableType"
27 |
28 | def __init__(
29 | self,
30 | *renderables: "RenderableType",
31 | style: Optional[StyleType] = None,
32 | application_mode: bool = False,
33 | ) -> None:
34 | from rich.console import Group
35 |
36 | self.renderable = Group(*renderables)
37 | self.style = style
38 | self.application_mode = application_mode
39 |
40 | def __rich_console__(
41 | self, console: "Console", options: "ConsoleOptions"
42 | ) -> "RenderResult":
43 | width, height = options.size
44 | style = console.get_style(self.style) if self.style else None
45 | render_options = options.update(width=width, height=height)
46 | lines = console.render_lines(
47 | self.renderable or "", render_options, style=style, pad=True
48 | )
49 | lines = Segment.set_shape(lines, width, height, style=style)
50 | new_line = Segment("\n\r") if self.application_mode else Segment.line()
51 | for last, line in loop_last(lines):
52 | yield from line
53 | if not last:
54 | yield new_line
55 |
--------------------------------------------------------------------------------
/rich/styled.py:
--------------------------------------------------------------------------------
1 | from typing import TYPE_CHECKING
2 |
3 | from .measure import Measurement
4 | from .segment import Segment
5 | from .style import StyleType
6 |
7 | if TYPE_CHECKING:
8 | from .console import Console, ConsoleOptions, RenderResult, RenderableType
9 |
10 |
11 | class Styled:
12 | """Apply a style to a renderable.
13 |
14 | Args:
15 | renderable (RenderableType): Any renderable.
16 | style (StyleType): A style to apply across the entire renderable.
17 | """
18 |
19 | def __init__(self, renderable: "RenderableType", style: "StyleType") -> None:
20 | self.renderable = renderable
21 | self.style = style
22 |
23 | def __rich_console__(
24 | self, console: "Console", options: "ConsoleOptions"
25 | ) -> "RenderResult":
26 | style = console.get_style(self.style)
27 | rendered_segments = console.render(self.renderable, options)
28 | segments = Segment.apply_style(rendered_segments, style)
29 | return segments
30 |
31 | def __rich_measure__(
32 | self, console: "Console", options: "ConsoleOptions"
33 | ) -> Measurement:
34 | return Measurement.get(console, options, self.renderable)
35 |
36 |
37 | if __name__ == "__main__": # pragma: no cover
38 | from rich import print
39 | from rich.panel import Panel
40 |
41 | panel = Styled(Panel("hello"), "on blue")
42 | print(panel)
43 |
--------------------------------------------------------------------------------
/rich/themes.py:
--------------------------------------------------------------------------------
1 | from .default_styles import DEFAULT_STYLES
2 | from .theme import Theme
3 |
4 |
5 | DEFAULT = Theme(DEFAULT_STYLES)
6 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This is a shim to hopefully allow Github to detect the package, build is done with poetry
4 |
5 | import setuptools
6 |
7 | if __name__ == "__main__":
8 | setuptools.setup(name="rich")
9 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Textualize/rich/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/tests/__init__.py
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.fixture(autouse=True)
5 | def reset_color_envvars(monkeypatch):
6 | """Remove color-related envvars to fix test output"""
7 | monkeypatch.delenv("FORCE_COLOR", raising=False)
8 | monkeypatch.delenv("NO_COLOR", raising=False)
9 |
--------------------------------------------------------------------------------
/tests/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | junit_family=legacy
3 |
--------------------------------------------------------------------------------
/tests/render.py:
--------------------------------------------------------------------------------
1 | import io
2 | import re
3 |
4 | from rich.console import Console, RenderableType
5 |
6 |
7 | re_link_ids = re.compile(r"id=[\d.\-]*?;.*?\x1b")
8 |
9 |
10 | def replace_link_ids(render: str) -> str:
11 | """Link IDs have a random ID and system path which is a problem for
12 | reproducible tests.
13 |
14 | """
15 | return re_link_ids.sub("id=0;foo\x1b", render)
16 |
17 |
18 | def render(renderable: RenderableType, no_wrap: bool = False) -> str:
19 | console = Console(
20 | width=100, file=io.StringIO(), color_system="truecolor", legacy_windows=False
21 | )
22 | console.print(renderable, no_wrap=no_wrap)
23 | output = replace_link_ids(console.file.getvalue())
24 | return output
25 |
--------------------------------------------------------------------------------
/tests/test_ansi.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from rich.ansi import AnsiDecoder
4 | from rich.console import Console
5 | from rich.style import Style
6 | from rich.text import Span, Text
7 |
8 |
9 | def test_decode():
10 | console = Console(
11 | force_terminal=True, legacy_windows=False, color_system="truecolor"
12 | )
13 | console.begin_capture()
14 | console.print("Hello")
15 | console.print("[b]foo[/b]")
16 | console.print("[link http://example.org]bar")
17 | console.print("[#ff0000 on color(200)]red")
18 | console.print("[color(200) on #ff0000]red")
19 | terminal_codes = console.end_capture()
20 |
21 | decoder = AnsiDecoder()
22 | lines = list(decoder.decode(terminal_codes))
23 |
24 | expected = [
25 | Text("Hello"),
26 | Text("foo", spans=[Span(0, 3, Style.parse("bold"))]),
27 | Text("bar", spans=[Span(0, 3, Style.parse("link http://example.org"))]),
28 | Text("red", spans=[Span(0, 3, Style.parse("#ff0000 on color(200)"))]),
29 | Text("red", spans=[Span(0, 3, Style.parse("color(200) on #ff0000"))]),
30 | ]
31 |
32 | assert lines == expected
33 |
34 |
35 | def test_decode_example():
36 | ansi_bytes = b"\x1b[01m\x1b[KC:\\Users\\stefa\\AppData\\Local\\Temp\\tmp3ydingba:\x1b[m\x1b[K In function '\x1b[01m\x1b[Kmain\x1b[m\x1b[K':\n\x1b[01m\x1b[KC:\\Users\\stefa\\AppData\\Local\\Temp\\tmp3ydingba:3:5:\x1b[m\x1b[K \x1b[01;35m\x1b[Kwarning: \x1b[m\x1b[Kunused variable '\x1b[01m\x1b[Ka\x1b[m\x1b[K' [\x1b[01;35m\x1b[K-Wunused-variable\x1b[m\x1b[K]\n 3 | int \x1b[01;35m\x1b[Ka\x1b[m\x1b[K=1;\n | \x1b[01;35m\x1b[K^\x1b[m\x1b[K\n"
37 | ansi_text = ansi_bytes.decode("utf-8")
38 |
39 | text = Text.from_ansi(ansi_text)
40 |
41 | console = Console(
42 | force_terminal=True, legacy_windows=False, color_system="truecolor"
43 | )
44 | with console.capture() as capture:
45 | console.print(text)
46 | result = capture.get()
47 | print(repr(result))
48 | expected = "\x1b[1mC:\\Users\\stefa\\AppData\\Local\\Temp\\tmp3ydingba:\x1b[0m In function '\x1b[1mmain\x1b[0m':\n\x1b[1mC:\\Users\\stefa\\AppData\\Local\\Temp\\tmp3ydingba:3:5:\x1b[0m \x1b[1;35mwarning: \x1b[0munused variable '\x1b[1ma\x1b[0m' \n[\x1b[1;35m-Wunused-variable\x1b[0m]\n 3 | int \x1b[1;35ma\x1b[0m=1;\n | \x1b[1;35m^\x1b[0m\n"
49 | assert result == expected
50 |
51 |
52 | @pytest.mark.parametrize(
53 | "ansi_bytes, expected_text",
54 | [
55 | # https://github.com/Textualize/rich/issues/2688
56 | (
57 | b"\x1b[31mFound 4 errors in 2 files (checked 18 source files)\x1b(B\x1b[m\n",
58 | "Found 4 errors in 2 files (checked 18 source files)",
59 | ),
60 | # https://mail.python.org/pipermail/python-list/2007-December/424756.html
61 | (b"Hallo", "Hallo"),
62 | (b"\x1b(BHallo", "Hallo"),
63 | (b"\x1b(JHallo", "Hallo"),
64 | (b"\x1b(BHal\x1b(Jlo", "Hallo"),
65 | ],
66 | )
67 | def test_decode_issue_2688(ansi_bytes, expected_text):
68 | text = Text.from_ansi(ansi_bytes.decode())
69 |
70 | assert str(text) == expected_text
71 |
72 |
73 | @pytest.mark.parametrize("code", [*"0123456789:;<=>?"])
74 | def test_strip_private_escape_sequences(code):
75 | text = Text.from_ansi(f"\x1b{code}x")
76 |
77 | console = Console(force_terminal=True)
78 |
79 | with console.capture() as capture:
80 | console.print(text)
81 |
82 | expected = "x\n"
83 |
84 | assert capture.get() == expected
85 |
--------------------------------------------------------------------------------
/tests/test_block_bar.py:
--------------------------------------------------------------------------------
1 | from rich.bar import Bar
2 | from rich.console import Console
3 |
4 | from .render import render
5 |
6 |
7 | expected = [
8 | "\x1b[39;49m ▐█████████████████████████ \x1b[0m\n",
9 | "\x1b[39;49m ██████████████████████▌ \x1b[0m\n",
10 | "\x1b[39;49m \x1b[0m\n",
11 | ]
12 |
13 |
14 | def test_repr():
15 | bar = Bar(size=100, begin=11, end=62, width=50)
16 | assert repr(bar) == "Bar(100, 11, 62)"
17 |
18 |
19 | def test_render():
20 | bar = Bar(size=100, begin=11, end=62, width=50)
21 | bar_render = render(bar)
22 | assert bar_render == expected[0]
23 | bar = Bar(size=100, begin=12, end=57, width=50)
24 | bar_render = render(bar)
25 | assert bar_render == expected[1]
26 | # begin after end
27 | bar = Bar(size=100, begin=60, end=40, width=50)
28 | bar_render = render(bar)
29 | assert bar_render == expected[2]
30 |
31 |
32 | def test_measure():
33 | console = Console(width=120)
34 | bar = Bar(size=100, begin=11, end=62)
35 | measurement = bar.__rich_measure__(console, console.options)
36 | assert measurement.minimum == 4
37 | assert measurement.maximum == 120
38 |
39 |
40 | def test_zero_total():
41 | # Shouldn't throw zero division error
42 | bar = Bar(size=0, begin=0, end=0)
43 | render(bar)
44 |
45 |
46 | if __name__ == "__main__":
47 | bar = Bar(size=100, begin=11, end=62, width=50)
48 | bar_render = render(bar)
49 | print(repr(bar_render))
50 | bar = Bar(size=100, begin=12, end=57, width=50)
51 | bar_render = render(bar)
52 | print(repr(bar_render))
53 | bar = Bar(size=100, begin=60, end=40, width=50)
54 | bar_render = render(bar)
55 | print(repr(bar_render))
56 |
--------------------------------------------------------------------------------
/tests/test_box.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from rich.console import ConsoleOptions, ConsoleDimensions
4 | from rich.box import (
5 | ASCII,
6 | DOUBLE,
7 | ROUNDED,
8 | HEAVY,
9 | SQUARE,
10 | MINIMAL_HEAVY_HEAD,
11 | MINIMAL,
12 | SIMPLE_HEAVY,
13 | SIMPLE,
14 | HEAVY_EDGE,
15 | HEAVY_HEAD,
16 | )
17 |
18 |
19 | def test_str():
20 | assert str(ASCII) == "+--+\n| ||\n|-+|\n| ||\n|-+|\n|-+|\n| ||\n+--+\n"
21 |
22 |
23 | def test_repr():
24 | assert repr(ASCII) == "Box(...)"
25 |
26 |
27 | def test_get_top():
28 | top = HEAVY.get_top(widths=[1, 2])
29 | assert top == "┏━┳━━┓"
30 |
31 |
32 | def test_get_row():
33 | head_row = DOUBLE.get_row(widths=[3, 2, 1], level="head")
34 | assert head_row == "╠═══╬══╬═╣"
35 |
36 | row = ASCII.get_row(widths=[1, 2, 3], level="row")
37 | assert row == "|-+--+---|"
38 |
39 | foot_row = ROUNDED.get_row(widths=[2, 1, 3], level="foot")
40 | assert foot_row == "├──┼─┼───┤"
41 |
42 | with pytest.raises(ValueError):
43 | ROUNDED.get_row(widths=[1, 2, 3], level="FOO")
44 |
45 |
46 | def test_get_bottom():
47 | bottom = HEAVY.get_bottom(widths=[1, 2, 3])
48 | assert bottom == "┗━┻━━┻━━━┛"
49 |
50 |
51 | def test_box_substitute_for_same_box():
52 | options = ConsoleOptions(
53 | ConsoleDimensions(80, 25),
54 | legacy_windows=False,
55 | min_width=1,
56 | max_width=100,
57 | is_terminal=True,
58 | encoding="utf-8",
59 | max_height=25,
60 | )
61 |
62 | assert ROUNDED.substitute(options) == ROUNDED
63 | assert MINIMAL_HEAVY_HEAD.substitute(options) == MINIMAL_HEAVY_HEAD
64 | assert SIMPLE_HEAVY.substitute(options) == SIMPLE_HEAVY
65 | assert HEAVY.substitute(options) == HEAVY
66 | assert HEAVY_EDGE.substitute(options) == HEAVY_EDGE
67 | assert HEAVY_HEAD.substitute(options) == HEAVY_HEAD
68 |
69 |
70 | def test_box_substitute_for_different_box_legacy_windows():
71 | options = ConsoleOptions(
72 | ConsoleDimensions(80, 25),
73 | legacy_windows=True,
74 | min_width=1,
75 | max_width=100,
76 | is_terminal=True,
77 | encoding="utf-8",
78 | max_height=25,
79 | )
80 |
81 | assert ROUNDED.substitute(options) == SQUARE
82 | assert MINIMAL_HEAVY_HEAD.substitute(options) == MINIMAL
83 | assert SIMPLE_HEAVY.substitute(options) == SIMPLE
84 | assert HEAVY.substitute(options) == SQUARE
85 | assert HEAVY_EDGE.substitute(options) == SQUARE
86 | assert HEAVY_HEAD.substitute(options) == SQUARE
87 |
88 |
89 | def test_box_substitute_for_different_box_ascii_encoding():
90 | options = ConsoleOptions(
91 | ConsoleDimensions(80, 25),
92 | legacy_windows=True,
93 | min_width=1,
94 | max_width=100,
95 | is_terminal=True,
96 | encoding="ascii",
97 | max_height=25,
98 | )
99 |
100 | assert ROUNDED.substitute(options) == ASCII
101 | assert MINIMAL_HEAVY_HEAD.substitute(options) == ASCII
102 | assert SIMPLE_HEAVY.substitute(options) == ASCII
103 | assert HEAVY.substitute(options) == ASCII
104 | assert HEAVY_EDGE.substitute(options) == ASCII
105 | assert HEAVY_HEAD.substitute(options) == ASCII
106 |
--------------------------------------------------------------------------------
/tests/test_card.py:
--------------------------------------------------------------------------------
1 | import io
2 | import re
3 |
4 | from rich.__main__ import make_test_card
5 | from rich.console import Console, RenderableType
6 |
7 | from ._card_render import expected
8 |
9 | re_link_ids = re.compile(r"id=[\d\.\-]*?;.*?\x1b")
10 |
11 |
12 | def replace_link_ids(render: str) -> str:
13 | """Link IDs have a random ID and system path which is a problem for
14 | reproducible tests.
15 |
16 | """
17 | return re_link_ids.sub("id=0;foo\x1b", render)
18 |
19 |
20 | def render(renderable: RenderableType) -> str:
21 | console = Console(
22 | width=100, file=io.StringIO(), color_system="truecolor", legacy_windows=False
23 | )
24 | console.print(renderable)
25 | output = replace_link_ids(console.file.getvalue())
26 | return output
27 |
28 |
29 | def test_card_render():
30 | card = make_test_card()
31 | result = render(card)
32 | print(repr(result))
33 | assert result == expected
34 |
35 |
36 | if __name__ == "__main__":
37 | card = make_test_card()
38 | with open("_card_render.py", "wt") as fh:
39 | card_render = render(card)
40 | print(card_render)
41 | fh.write(f"expected={card_render!r}")
42 |
--------------------------------------------------------------------------------
/tests/test_cells.py:
--------------------------------------------------------------------------------
1 | import string
2 |
3 | from rich import cells
4 | from rich.cells import _is_single_cell_widths, chop_cells
5 |
6 |
7 | def test_cell_len_long_string():
8 | # Long strings don't use cached cell length implementation
9 | assert cells.cell_len("abc" * 200) == 3 * 200
10 | # Boundary case
11 | assert cells.cell_len("a" * 512) == 512
12 |
13 |
14 | def test_cell_len_short_string():
15 | # Short strings use cached cell length implementation
16 | assert cells.cell_len("abc" * 100) == 3 * 100
17 | # Boundary case
18 | assert cells.cell_len("a" * 511) == 511
19 |
20 |
21 | def test_set_cell_size():
22 | assert cells.set_cell_size("foo", 0) == ""
23 | assert cells.set_cell_size("f", 0) == ""
24 | assert cells.set_cell_size("", 0) == ""
25 | assert cells.set_cell_size("😽😽", 0) == ""
26 | assert cells.set_cell_size("foo", 2) == "fo"
27 | assert cells.set_cell_size("foo", 3) == "foo"
28 | assert cells.set_cell_size("foo", 4) == "foo "
29 | assert cells.set_cell_size("😽😽", 4) == "😽😽"
30 | assert cells.set_cell_size("😽😽", 3) == "😽 "
31 | assert cells.set_cell_size("😽😽", 2) == "😽"
32 | assert cells.set_cell_size("😽😽", 1) == " "
33 | assert cells.set_cell_size("😽😽", 5) == "😽😽 "
34 |
35 |
36 | def test_set_cell_size_infinite():
37 | for size in range(38):
38 | assert (
39 | cells.cell_len(
40 | cells.set_cell_size(
41 | "เป็นเกมที่ต้องมีความอดทนมากที่สุดตั้งเเต่เคยเล่นมา", size
42 | )
43 | )
44 | == size
45 | )
46 |
47 |
48 | def test_chop_cells():
49 | """Simple example of splitting cells into lines of width 3."""
50 | text = "abcdefghijk"
51 | assert chop_cells(text, 3) == ["abc", "def", "ghi", "jk"]
52 |
53 |
54 | def test_chop_cells_double_width_boundary():
55 | """The available width lies within a double-width character."""
56 | text = "ありがとう"
57 | assert chop_cells(text, 3) == ["あ", "り", "が", "と", "う"]
58 |
59 |
60 | def test_chop_cells_mixed_width():
61 | """Mixed single and double-width characters."""
62 | text = "あ1り234が5と6う78"
63 | assert chop_cells(text, 3) == ["あ1", "り2", "34", "が5", "と6", "う7", "8"]
64 |
65 |
66 | def test_is_single_cell_widths() -> None:
67 | # Check _is_single_cell_widths reports correctly
68 | for character in string.printable:
69 | if ord(character) >= 32:
70 | assert _is_single_cell_widths(character)
71 |
72 | BOX = "┌─┬┐│ ││├─┼┤│ ││├─┼┤├─┼┤│ ││└─┴┘"
73 |
74 | for character in BOX:
75 | assert _is_single_cell_widths(character)
76 |
77 | for character in "💩😽":
78 | assert not _is_single_cell_widths(character)
79 |
80 | for character in "わさび":
81 | assert not _is_single_cell_widths(character)
82 |
--------------------------------------------------------------------------------
/tests/test_color_triplet.py:
--------------------------------------------------------------------------------
1 | from rich.color_triplet import ColorTriplet
2 |
3 |
4 | def test_hex():
5 | assert ColorTriplet(255, 255, 255).hex == "#ffffff"
6 | assert ColorTriplet(0, 255, 0).hex == "#00ff00"
7 |
8 |
9 | def test_rgb():
10 | assert ColorTriplet(255, 255, 255).rgb == "rgb(255,255,255)"
11 | assert ColorTriplet(0, 255, 0).rgb == "rgb(0,255,0)"
12 |
13 |
14 | def test_normalized():
15 | assert ColorTriplet(255, 255, 255).normalized == (1.0, 1.0, 1.0)
16 | assert ColorTriplet(0, 255, 0).normalized == (0.0, 1.0, 0.0)
17 |
--------------------------------------------------------------------------------
/tests/test_columns_align.py:
--------------------------------------------------------------------------------
1 | # encoding=utf-8
2 |
3 | import io
4 |
5 | from rich import box
6 | from rich.columns import Columns
7 | from rich.console import Console
8 | from rich.panel import Panel
9 |
10 |
11 | def render():
12 | console = Console(file=io.StringIO(), width=100, legacy_windows=False)
13 | panel = Panel.fit("foo", box=box.SQUARE, padding=0)
14 | columns = Columns([panel] * 4)
15 | columns.expand = True
16 | console.rule("no align")
17 | console.print(columns)
18 |
19 | columns.align = "left"
20 | console.rule("left align")
21 | console.print(columns)
22 |
23 | columns.align = "center"
24 | console.rule("center align")
25 | console.print(columns)
26 |
27 | columns.align = "right"
28 | console.rule("right align")
29 | console.print(columns)
30 |
31 | return console.file.getvalue()
32 |
33 |
34 | def test_align():
35 | result = render()
36 | expected = "───────────────────────────────────────────── no align ─────────────────────────────────────────────\n┌───┐ ┌───┐ ┌───┐ ┌───┐ \n│foo│ │foo│ │foo│ │foo│ \n└───┘ └───┘ └───┘ └───┘ \n──────────────────────────────────────────── left align ────────────────────────────────────────────\n┌───┐ ┌───┐ ┌───┐ ┌───┐ \n│foo│ │foo│ │foo│ │foo│ \n└───┘ └───┘ └───┘ └───┘ \n─────────────────────────────────────────── center align ───────────────────────────────────────────\n ┌───┐ ┌───┐ ┌───┐ ┌───┐ \n │foo│ │foo│ │foo│ │foo│ \n └───┘ └───┘ └───┘ └───┘ \n─────────────────────────────────────────── right align ────────────────────────────────────────────\n ┌───┐ ┌───┐ ┌───┐ ┌───┐\n │foo│ │foo│ │foo│ │foo│\n └───┘ └───┘ └───┘ └───┘\n"
37 | assert result == expected
38 |
39 |
40 | if __name__ == "__main__":
41 | rendered = render()
42 | print(rendered)
43 | print(repr(rendered))
44 |
--------------------------------------------------------------------------------
/tests/test_constrain.py:
--------------------------------------------------------------------------------
1 | from rich.console import Console
2 | from rich.constrain import Constrain
3 | from rich.text import Text
4 |
5 |
6 | def test_width_of_none():
7 | console = Console()
8 | constrain = Constrain(Text("foo"), width=None)
9 | min_width, max_width = constrain.__rich_measure__(
10 | console, console.options.update_width(80)
11 | )
12 | assert min_width == 3
13 | assert max_width == 3
14 |
--------------------------------------------------------------------------------
/tests/test_containers.py:
--------------------------------------------------------------------------------
1 | from rich.console import Console
2 | from rich.containers import Lines, Renderables
3 | from rich.text import Span, Text
4 | from rich.style import Style
5 |
6 |
7 | def test_renderables_measure():
8 | console = Console()
9 | text = Text("foo")
10 | renderables = Renderables([text])
11 |
12 | result = renderables.__rich_measure__(console, console.options)
13 | _min, _max = result
14 | assert _min == 3
15 | assert _max == 3
16 |
17 | assert list(renderables) == [text]
18 |
19 |
20 | def test_renderables_empty():
21 | console = Console()
22 | renderables = Renderables()
23 |
24 | result = renderables.__rich_measure__(console, console.options)
25 | _min, _max = result
26 | assert _min == 1
27 | assert _max == 1
28 |
29 |
30 | def test_lines_rich_console():
31 | console = Console()
32 | lines = Lines([Text("foo")])
33 |
34 | result = list(lines.__rich_console__(console, console.options))
35 | assert result == [Text("foo")]
36 |
37 |
38 | def test_lines_justify():
39 | console = Console()
40 | lines1 = Lines([Text("foo", style="b"), Text("test", style="b")])
41 | lines1.justify(console, 10, justify="left")
42 | assert lines1._lines == [Text("foo "), Text("test ")]
43 | lines1.justify(console, 10, justify="center")
44 | assert lines1._lines == [Text(" foo "), Text(" test ")]
45 | lines1.justify(console, 10, justify="right")
46 | assert lines1._lines == [Text(" foo"), Text(" test")]
47 |
48 | lines2 = Lines([Text("foo bar", style="b"), Text("test", style="b")])
49 | lines2.justify(console, 7, justify="full")
50 | print(repr(lines2._lines[0].spans))
51 | assert lines2._lines == [
52 | Text(
53 | "foo bar",
54 | spans=[Span(0, 3, "b"), Span(3, 4, Style.parse("bold")), Span(4, 7, "b")],
55 | ),
56 | Text("test"),
57 | ]
58 |
--------------------------------------------------------------------------------
/tests/test_control.py:
--------------------------------------------------------------------------------
1 | from rich.control import Control, escape_control_codes, strip_control_codes
2 | from rich.segment import ControlType, Segment
3 |
4 |
5 | def test_control():
6 | control = Control(ControlType.BELL)
7 | assert str(control) == "\x07"
8 |
9 |
10 | def test_strip_control_codes():
11 | assert strip_control_codes("") == ""
12 | assert strip_control_codes("foo\rbar") == "foobar"
13 | assert strip_control_codes("Fear is the mind killer") == "Fear is the mind killer"
14 |
15 |
16 | def test_escape_control_codes():
17 | assert escape_control_codes("") == ""
18 | assert escape_control_codes("foo\rbar") == "foo\\rbar"
19 | assert escape_control_codes("Fear is the mind killer") == "Fear is the mind killer"
20 |
21 |
22 | def test_control_move_to():
23 | control = Control.move_to(5, 10)
24 | print(control.segment)
25 | assert control.segment == Segment(
26 | "\x1b[11;6H", None, [(ControlType.CURSOR_MOVE_TO, 5, 10)]
27 | )
28 |
29 |
30 | def test_control_move():
31 | assert Control.move(0, 0).segment == Segment("", None, [])
32 | control = Control.move(3, 4)
33 | print(repr(control.segment))
34 | assert control.segment == Segment(
35 | "\x1b[3C\x1b[4B",
36 | None,
37 | [(ControlType.CURSOR_FORWARD, 3), (ControlType.CURSOR_DOWN, 4)],
38 | )
39 |
40 |
41 | def test_move_to_column():
42 | print(repr(Control.move_to_column(10, 20).segment))
43 | assert Control.move_to_column(10, 20).segment == Segment(
44 | "\x1b[11G\x1b[20B",
45 | None,
46 | [(ControlType.CURSOR_MOVE_TO_COLUMN, 10), (ControlType.CURSOR_DOWN, 20)],
47 | )
48 |
49 | assert Control.move_to_column(10, -20).segment == Segment(
50 | "\x1b[11G\x1b[20A",
51 | None,
52 | [(ControlType.CURSOR_MOVE_TO_COLUMN, 10), (ControlType.CURSOR_UP, 20)],
53 | )
54 |
55 |
56 | def test_title():
57 | control_segment = Control.title("hello").segment
58 | assert control_segment == Segment(
59 | "\x1b]0;hello\x07",
60 | None,
61 | [(ControlType.SET_WINDOW_TITLE, "hello")],
62 | )
63 |
--------------------------------------------------------------------------------
/tests/test_emoji.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from rich.emoji import Emoji, NoEmoji
4 |
5 | from .render import render
6 |
7 |
8 | def test_no_emoji():
9 | with pytest.raises(NoEmoji):
10 | Emoji("ambivalent_bunny")
11 |
12 |
13 | def test_str_repr():
14 | assert str(Emoji("pile_of_poo")) == "💩"
15 | assert repr(Emoji("pile_of_poo")) == ""
16 |
17 |
18 | def test_replace():
19 | assert Emoji.replace("my code is :pile_of_poo:") == "my code is 💩"
20 |
21 |
22 | def test_render():
23 | render_result = render(Emoji("pile_of_poo"))
24 | assert render_result == "💩"
25 |
26 |
27 | def test_variant():
28 | print(repr(Emoji.replace(":warning:")))
29 | assert Emoji.replace(":warning:") == "⚠"
30 | assert Emoji.replace(":warning-text:") == "⚠" + "\uFE0E"
31 | assert Emoji.replace(":warning-emoji:") == "⚠" + "\uFE0F"
32 | assert Emoji.replace(":warning-foo:") == ":warning-foo:"
33 |
34 |
35 | def test_variant_non_default():
36 | render_result = render(Emoji("warning", variant="emoji"))
37 | assert render_result == "⚠" + "\uFE0F"
38 |
--------------------------------------------------------------------------------
/tests/test_file_proxy.py:
--------------------------------------------------------------------------------
1 | import io
2 | import sys
3 |
4 | import pytest
5 |
6 | from rich.console import Console
7 | from rich.file_proxy import FileProxy
8 |
9 |
10 | def test_empty_bytes():
11 | console = Console()
12 | file_proxy = FileProxy(console, sys.stdout)
13 | # File should raise TypeError when writing bytes
14 | with pytest.raises(TypeError):
15 | file_proxy.write(b"") # type: ignore
16 | with pytest.raises(TypeError):
17 | file_proxy.write(b"foo") # type: ignore
18 |
19 |
20 | def test_flush():
21 | file = io.StringIO()
22 | console = Console(file=file)
23 | file_proxy = FileProxy(console, file)
24 | file_proxy.write("foo")
25 | assert file.getvalue() == ""
26 | file_proxy.flush()
27 | assert file.getvalue() == "foo\n"
28 |
29 |
30 | def test_new_lines():
31 | file = io.StringIO()
32 | console = Console(file=file)
33 | file_proxy = FileProxy(console, file)
34 | file_proxy.write("-\n-")
35 | assert file.getvalue() == "-\n"
36 | file_proxy.flush()
37 | assert file.getvalue() == "-\n-\n"
38 |
--------------------------------------------------------------------------------
/tests/test_filesize.py:
--------------------------------------------------------------------------------
1 | from rich import filesize
2 |
3 |
4 | def test_traditional():
5 | assert filesize.decimal(0) == "0 bytes"
6 | assert filesize.decimal(1) == "1 byte"
7 | assert filesize.decimal(2) == "2 bytes"
8 | assert filesize.decimal(1000) == "1.0 kB"
9 | assert filesize.decimal(1.5 * 1000 * 1000) == "1.5 MB"
10 | assert filesize.decimal(0, precision=2) == "0 bytes"
11 | assert filesize.decimal(1111, precision=0) == "1 kB"
12 | assert filesize.decimal(1111, precision=1) == "1.1 kB"
13 | assert filesize.decimal(1111, precision=2) == "1.11 kB"
14 | assert filesize.decimal(1111, separator="") == "1.1kB"
15 |
16 |
17 | def test_pick_unit_and_suffix():
18 | units = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
19 | assert filesize.pick_unit_and_suffix(50, units, 1024) == (1, "bytes")
20 | assert filesize.pick_unit_and_suffix(2048, units, 1024) == (1024, "KB")
21 |
--------------------------------------------------------------------------------
/tests/test_getfileno.py:
--------------------------------------------------------------------------------
1 | from rich._fileno import get_fileno
2 |
3 |
4 | def test_get_fileno():
5 | class FileLike:
6 | def fileno(self) -> int:
7 | return 123
8 |
9 | assert get_fileno(FileLike()) == 123
10 |
11 |
12 | def test_get_fileno_missing():
13 | class FileLike:
14 | pass
15 |
16 | assert get_fileno(FileLike()) is None
17 |
18 |
19 | def test_get_fileno_broken():
20 | class FileLike:
21 | def fileno(self) -> int:
22 | 1 / 0
23 | return 123
24 |
25 | assert get_fileno(FileLike()) is None
26 |
--------------------------------------------------------------------------------
/tests/test_json.py:
--------------------------------------------------------------------------------
1 | from rich.json import JSON
2 | import datetime
3 |
4 |
5 | def test_print_json_data_with_default():
6 | date = datetime.date(2021, 1, 1)
7 | json = JSON.from_data({"date": date}, default=lambda d: d.isoformat())
8 | assert str(json.text) == '{\n "date": "2021-01-01"\n}'
9 |
--------------------------------------------------------------------------------
/tests/test_jupyter.py:
--------------------------------------------------------------------------------
1 | from rich.console import Console
2 |
3 |
4 | def test_jupyter():
5 | console = Console(force_jupyter=True)
6 | assert console.width == 115
7 | assert console.height == 100
8 | assert console.color_system == "truecolor"
9 |
10 |
11 | def test_jupyter_columns_env():
12 | console = Console(_environ={"JUPYTER_COLUMNS": "314"}, force_jupyter=True)
13 | assert console.width == 314
14 | # width take precedence
15 | console = Console(width=40, _environ={"JUPYTER_COLUMNS": "314"}, force_jupyter=True)
16 | assert console.width == 40
17 | # Should not fail
18 | console = Console(
19 | width=40, _environ={"JUPYTER_COLUMNS": "broken"}, force_jupyter=True
20 | )
21 |
22 |
23 | def test_jupyter_lines_env():
24 | console = Console(_environ={"JUPYTER_LINES": "220"}, force_jupyter=True)
25 | assert console.height == 220
26 | # height take precedence
27 | console = Console(height=40, _environ={"JUPYTER_LINES": "220"}, force_jupyter=True)
28 | assert console.height == 40
29 | # Should not fail
30 | console = Console(
31 | width=40, _environ={"JUPYTER_LINES": "broken"}, force_jupyter=True
32 | )
33 |
--------------------------------------------------------------------------------
/tests/test_live_render.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from rich.live_render import LiveRender
3 | from rich.console import Console, ConsoleDimensions, ConsoleOptions
4 | from rich.style import Style
5 | from rich.segment import Segment
6 |
7 |
8 | @pytest.fixture
9 | def live_render():
10 | return LiveRender(renderable="my string")
11 |
12 |
13 | def test_renderable(live_render):
14 | assert live_render.renderable == "my string"
15 | live_render.set_renderable("another string")
16 | assert live_render.renderable == "another string"
17 |
18 |
19 | def test_position_cursor(live_render):
20 | assert str(live_render.position_cursor()) == ""
21 | live_render._shape = (80, 2)
22 | assert str(live_render.position_cursor()) == "\r\x1b[2K\x1b[1A\x1b[2K"
23 |
24 |
25 | def test_restore_cursor(live_render):
26 | assert str(live_render.restore_cursor()) == ""
27 | live_render._shape = (80, 2)
28 | assert str(live_render.restore_cursor()) == "\r\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
29 |
30 |
31 | def test_rich_console(live_render):
32 | options = ConsoleOptions(
33 | ConsoleDimensions(80, 25),
34 | max_height=25,
35 | legacy_windows=False,
36 | min_width=10,
37 | max_width=20,
38 | is_terminal=False,
39 | encoding="utf-8",
40 | )
41 | rich_console = live_render.__rich_console__(Console(), options)
42 | assert [Segment("my string", None)] == list(rich_console)
43 | live_render.style = "red"
44 | rich_console = live_render.__rich_console__(Console(), options)
45 | assert [Segment("my string", Style.parse("red"))] == list(rich_console)
46 |
--------------------------------------------------------------------------------
/tests/test_log.py:
--------------------------------------------------------------------------------
1 | # encoding=utf-8
2 |
3 |
4 | import io
5 | import re
6 |
7 | from rich.console import Console
8 |
9 | re_link_ids = re.compile(r"id=[\d\.\-]*?;.*?\x1b")
10 |
11 |
12 | def replace_link_ids(render: str) -> str:
13 | """Link IDs have a random ID and system path which is a problem for
14 | reproducible tests.
15 |
16 | """
17 | return re_link_ids.sub("id=0;foo\x1b", render)
18 |
19 |
20 | test_data = [1, 2, 3]
21 |
22 |
23 | def render_log():
24 | console = Console(
25 | file=io.StringIO(),
26 | width=80,
27 | force_terminal=True,
28 | log_time_format="[TIME]",
29 | color_system="truecolor",
30 | legacy_windows=False,
31 | )
32 | console.log()
33 | console.log("Hello from", console, "!")
34 | console.log(test_data, log_locals=True)
35 | return replace_link_ids(console.file.getvalue()).replace("test_log.py", "source.py")
36 |
37 |
38 | def test_log():
39 | expected = replace_link_ids(
40 | "\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[34m \x1b[0m\x1b[3;34mlocals\x1b[0m\x1b[34m \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"
41 | )
42 | rendered = render_log()
43 | print(repr(rendered))
44 | assert rendered == expected
45 |
46 |
47 | def test_log_caller_frame_info():
48 | for i in range(2):
49 | assert Console._caller_frame_info(i) == Console._caller_frame_info(
50 | i, lambda: None
51 | )
52 |
53 |
54 | def test_justify():
55 | console = Console(width=20, log_path=False, log_time=False, color_system=None)
56 | console.begin_capture()
57 | console.log("foo", justify="right")
58 | result = console.end_capture()
59 | assert result == " foo\n"
60 |
61 |
62 | if __name__ == "__main__":
63 | render = render_log()
64 | print(render)
65 | print(repr(render))
66 |
--------------------------------------------------------------------------------
/tests/test_measure.py:
--------------------------------------------------------------------------------
1 | from rich.text import Text
2 | import pytest
3 |
4 | from rich.errors import NotRenderableError
5 | from rich.console import Console
6 | from rich.measure import Measurement, measure_renderables
7 |
8 |
9 | def test_span():
10 | measurement = Measurement(10, 100)
11 | assert measurement.span == 90
12 |
13 |
14 | def test_no_renderable():
15 | console = Console()
16 | text = Text()
17 |
18 | with pytest.raises(NotRenderableError):
19 | Measurement.get(console, console.options, None)
20 |
21 |
22 | def test_measure_renderables():
23 | console = Console()
24 | assert measure_renderables(console, console.options, "") == Measurement(0, 0)
25 | assert measure_renderables(
26 | console, console.options.update_width(0), "hello"
27 | ) == Measurement(0, 0)
28 |
29 |
30 | def test_clamp():
31 | measurement = Measurement(20, 100)
32 | assert measurement.clamp(10, 50) == Measurement(20, 50)
33 | assert measurement.clamp(30, 50) == Measurement(30, 50)
34 | assert measurement.clamp(None, 50) == Measurement(20, 50)
35 | assert measurement.clamp(30, None) == Measurement(30, 100)
36 | assert measurement.clamp(None, None) == Measurement(20, 100)
37 |
--------------------------------------------------------------------------------
/tests/test_null_file.py:
--------------------------------------------------------------------------------
1 | from rich._null_file import NullFile
2 |
3 |
4 | def test_null_file():
5 | file = NullFile()
6 | with file:
7 | assert file.write("abc") == 0
8 | assert file.close() is None
9 | assert not file.isatty()
10 | assert file.read() == ""
11 | assert not file.readable()
12 | assert file.readline() == ""
13 | assert file.readlines() == []
14 | assert file.seek(0, 0) == 0
15 | assert not file.seekable()
16 | assert file.tell() == 0
17 | assert file.truncate() == 0
18 | assert file.writable() == False
19 | assert file.writelines([""]) is None
20 | assert next(file) == ""
21 | assert next(iter(file)) == ""
22 | assert file.fileno() == -1
23 | assert file.flush() is None
24 |
--------------------------------------------------------------------------------
/tests/test_padding.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from rich.padding import Padding
4 | from rich.console import Console, ConsoleDimensions, ConsoleOptions
5 | from rich.style import Style
6 | from rich.segment import Segment
7 |
8 |
9 | def test_repr():
10 | padding = Padding("foo", (1, 2))
11 | assert isinstance(repr(padding), str)
12 |
13 |
14 | def test_indent():
15 | indent_result = Padding.indent("test", 4)
16 | assert indent_result.top == 0
17 | assert indent_result.right == 0
18 | assert indent_result.bottom == 0
19 | assert indent_result.left == 4
20 |
21 |
22 | def test_unpack():
23 | assert Padding.unpack(3) == (3, 3, 3, 3)
24 | assert Padding.unpack((3,)) == (3, 3, 3, 3)
25 | assert Padding.unpack((3, 4)) == (3, 4, 3, 4)
26 | assert Padding.unpack((3, 4, 5, 6)) == (3, 4, 5, 6)
27 | with pytest.raises(ValueError):
28 | Padding.unpack((1, 2, 3))
29 |
30 |
31 | def test_expand_false():
32 | console = Console(width=100, color_system=None)
33 | console.begin_capture()
34 | console.print(Padding("foo", 1, expand=False))
35 | assert console.end_capture() == " \n foo \n \n"
36 |
37 |
38 | def test_rich_console():
39 | renderable = "test renderable"
40 | style = Style(color="red")
41 | options = ConsoleOptions(
42 | ConsoleDimensions(80, 25),
43 | max_height=25,
44 | legacy_windows=False,
45 | min_width=10,
46 | max_width=20,
47 | is_terminal=False,
48 | encoding="utf-8",
49 | )
50 |
51 | expected_outputs = [
52 | Segment(renderable, style=style),
53 | Segment(" " * (20 - len(renderable)), style=style),
54 | Segment("\n", style=None),
55 | ]
56 | padding_generator = Padding(renderable, style=style).__rich_console__(
57 | Console(), options
58 | )
59 | for output, expected in zip(padding_generator, expected_outputs):
60 | assert output == expected
61 |
--------------------------------------------------------------------------------
/tests/test_palette.py:
--------------------------------------------------------------------------------
1 | from rich._palettes import STANDARD_PALETTE
2 | from rich.table import Table
3 |
4 |
5 | def test_rich_cast():
6 | table = STANDARD_PALETTE.__rich__()
7 | assert isinstance(table, Table)
8 | assert table.row_count == 16
9 |
--------------------------------------------------------------------------------
/tests/test_pick.py:
--------------------------------------------------------------------------------
1 | from rich._pick import pick_bool
2 |
3 |
4 | def test_pick_bool():
5 | assert pick_bool(False) == False
6 | assert pick_bool(True) == True
7 | assert pick_bool(None) == False
8 | assert pick_bool(False, True) == False
9 | assert pick_bool(None, True) == True
10 | assert pick_bool(True, None) == True
11 | assert pick_bool(False, None) == False
12 | assert pick_bool(None, None) == False
13 | assert pick_bool(None, None, False, True) == False
14 | assert pick_bool(None, None, True, False) == True
15 |
--------------------------------------------------------------------------------
/tests/test_protocol.py:
--------------------------------------------------------------------------------
1 | import io
2 |
3 | from rich.abc import RichRenderable
4 | from rich.console import Console
5 | from rich.panel import Panel
6 | from rich.text import Text
7 |
8 |
9 | class Foo:
10 | def __rich__(self) -> Text:
11 | return Text("Foo")
12 |
13 |
14 | def test_rich_cast():
15 | foo = Foo()
16 | console = Console(file=io.StringIO())
17 | console.print(foo)
18 | assert console.file.getvalue() == "Foo\n"
19 |
20 |
21 | class Fake:
22 | def __getattr__(self, name):
23 | return 12
24 |
25 | def __repr__(self) -> str:
26 | return "Fake()"
27 |
28 |
29 | def test_rich_cast_fake():
30 | fake = Fake()
31 | console = Console(file=io.StringIO())
32 | console.print(fake)
33 | assert console.file.getvalue() == "Fake()\n"
34 |
35 |
36 | def test_rich_cast_container():
37 | foo = Foo()
38 | console = Console(file=io.StringIO(), legacy_windows=False)
39 | console.print(Panel.fit(foo, padding=0))
40 | assert console.file.getvalue() == "╭───╮\n│Foo│\n╰───╯\n"
41 |
42 |
43 | def test_abc():
44 | foo = Foo()
45 | assert isinstance(foo, RichRenderable)
46 | assert isinstance(Text("hello"), RichRenderable)
47 | assert isinstance(Panel("hello"), RichRenderable)
48 | assert not isinstance(foo, str)
49 | assert not isinstance("foo", RichRenderable)
50 | assert not isinstance([], RichRenderable)
51 |
52 |
53 | def test_cast_deep():
54 | class B:
55 | def __rich__(self) -> Foo:
56 | return Foo()
57 |
58 | class A:
59 | def __rich__(self) -> B:
60 | return B()
61 |
62 | console = Console(file=io.StringIO())
63 | console.print(A())
64 | assert console.file.getvalue() == "Foo\n"
65 |
66 |
67 | def test_cast_recursive():
68 | class B:
69 | def __rich__(self) -> "A":
70 | return A()
71 |
72 | def __repr__(self) -> str:
73 | return ""
74 |
75 | class A:
76 | def __rich__(self) -> B:
77 | return B()
78 |
79 | def __repr__(self) -> str:
80 | return ""
81 |
82 | console = Console(file=io.StringIO())
83 | console.print(A())
84 | assert console.file.getvalue() == "\n"
85 |
--------------------------------------------------------------------------------
/tests/test_ratio.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from typing import NamedTuple, Optional
3 |
4 | from rich._ratio import ratio_reduce, ratio_resolve
5 |
6 |
7 | class Edge(NamedTuple):
8 | size: Optional[int] = None
9 | ratio: int = 1
10 | minimum_size: int = 1
11 |
12 |
13 | @pytest.mark.parametrize(
14 | "total,ratios,maximums,values,result",
15 | [
16 | (20, [2, 4], [20, 20], [5, 5], [-2, -8]),
17 | (20, [2, 4], [1, 1], [5, 5], [4, 4]),
18 | (20, [2, 4], [1, 1], [2, 2], [1, 1]),
19 | (3, [2, 4], [3, 3], [2, 2], [1, 0]),
20 | (3, [2, 4], [3, 3], [0, 0], [-1, -2]),
21 | (3, [0, 0], [3, 3], [4, 4], [4, 4]),
22 | ],
23 | )
24 | def test_ratio_reduce(total, ratios, maximums, values, result):
25 | assert ratio_reduce(total, ratios, maximums, values) == result
26 |
27 |
28 | def test_ratio_resolve():
29 | assert ratio_resolve(100, []) == []
30 | assert ratio_resolve(100, [Edge(size=100), Edge(ratio=1)]) == [100, 1]
31 | assert ratio_resolve(100, [Edge(ratio=1)]) == [100]
32 | assert ratio_resolve(100, [Edge(ratio=1), Edge(ratio=1)]) == [50, 50]
33 | assert ratio_resolve(100, [Edge(size=20), Edge(ratio=1), Edge(ratio=1)]) == [
34 | 20,
35 | 40,
36 | 40,
37 | ]
38 | assert ratio_resolve(100, [Edge(size=40), Edge(ratio=2), Edge(ratio=1)]) == [
39 | 40,
40 | 40,
41 | 20,
42 | ]
43 | assert ratio_resolve(
44 | 100, [Edge(size=40), Edge(ratio=2), Edge(ratio=1, minimum_size=25)]
45 | ) == [40, 35, 25]
46 | assert ratio_resolve(100, [Edge(ratio=1), Edge(ratio=1), Edge(ratio=1)]) == [
47 | 33,
48 | 33,
49 | 34,
50 | ]
51 | assert ratio_resolve(
52 | 50, [Edge(size=30), Edge(ratio=1, minimum_size=10), Edge(size=30)]
53 | ) == [30, 10, 30]
54 | assert ratio_resolve(110, [Edge(ratio=1), Edge(ratio=1), Edge(ratio=1)]) == [
55 | 36,
56 | 37,
57 | 37,
58 | ]
59 |
--------------------------------------------------------------------------------
/tests/test_rich_print.py:
--------------------------------------------------------------------------------
1 | import io
2 | import json
3 |
4 | import rich
5 | from rich.console import Console
6 |
7 |
8 | def test_get_console():
9 | console = rich.get_console()
10 | assert isinstance(console, Console)
11 |
12 |
13 | def test_reconfigure_console():
14 | rich.reconfigure(width=100)
15 | assert rich.get_console().width == 100
16 |
17 |
18 | def test_rich_print():
19 | console = rich.get_console()
20 | output = io.StringIO()
21 | backup_file = console.file
22 | try:
23 | console.file = output
24 | rich.print("foo", "bar")
25 | rich.print("foo\n")
26 | rich.print("foo\n\n")
27 | assert output.getvalue() == "foo bar\nfoo\n\nfoo\n\n\n"
28 | finally:
29 | console.file = backup_file
30 |
31 |
32 | def test_rich_print_json():
33 | console = rich.get_console()
34 | with console.capture() as capture:
35 | rich.print_json('[false, true, null, "foo"]', indent=4)
36 | result = capture.get()
37 | print(repr(result))
38 | expected = '[\n false,\n true,\n null,\n "foo"\n]\n'
39 | assert result == expected
40 |
41 |
42 | def test_rich_print_json_round_trip():
43 | data = ["x" * 100, 2e128]
44 | console = rich.get_console()
45 | with console.capture() as capture:
46 | rich.print_json(data=data, indent=4)
47 | result = capture.get()
48 | print(repr(result))
49 | result_data = json.loads(result)
50 | assert result_data == data
51 |
52 |
53 | def test_rich_print_json_no_truncation():
54 | console = rich.get_console()
55 | with console.capture() as capture:
56 | rich.print_json(f'["{"x" * 100}", {int(2e128)}]', indent=4)
57 | result = capture.get()
58 | print(repr(result))
59 | assert ("x" * 100) in result
60 | assert str(int(2e128)) in result
61 |
62 |
63 | def test_rich_print_X():
64 | console = rich.get_console()
65 | output = io.StringIO()
66 | backup_file = console.file
67 | try:
68 | console.file = output
69 | rich.print("foo")
70 | rich.print("fooX")
71 | rich.print("fooXX")
72 | assert output.getvalue() == "foo\nfooX\nfooXX\n"
73 | finally:
74 | console.file = backup_file
75 |
--------------------------------------------------------------------------------
/tests/test_rule_in_table.py:
--------------------------------------------------------------------------------
1 | import io
2 | from textwrap import dedent
3 |
4 | import pytest
5 |
6 | from rich import box
7 | from rich.console import Console
8 | from rich.rule import Rule
9 | from rich.table import Table
10 |
11 |
12 | @pytest.mark.parametrize("expand_kwarg", ({}, {"expand": False}))
13 | def test_rule_in_unexpanded_table(expand_kwarg):
14 | console = Console(width=32, file=io.StringIO(), legacy_windows=False, _environ={})
15 | table = Table(box=box.ASCII, show_header=False, **expand_kwarg)
16 | table.add_column()
17 | table.add_column()
18 | table.add_row("COL1", "COL2")
19 | table.add_row("COL1", Rule())
20 | table.add_row("COL1", "COL2")
21 | console.print(table)
22 | expected = dedent(
23 | """\
24 | +-------------+
25 | | COL1 | COL2 |
26 | | COL1 | ──── |
27 | | COL1 | COL2 |
28 | +-------------+
29 | """
30 | )
31 | result = console.file.getvalue()
32 | assert result == expected
33 |
34 |
35 | def test_rule_in_expanded_table():
36 | console = Console(width=32, file=io.StringIO(), legacy_windows=False, _environ={})
37 | table = Table(box=box.ASCII, expand=True, show_header=False)
38 | table.add_column()
39 | table.add_column()
40 | table.add_row("COL1", "COL2")
41 | table.add_row("COL1", Rule(style=None))
42 | table.add_row("COL1", "COL2")
43 | console.print(table)
44 | expected = dedent(
45 | """\
46 | +------------------------------+
47 | | COL1 | COL2 |
48 | | COL1 | ──────────── |
49 | | COL1 | COL2 |
50 | +------------------------------+
51 | """
52 | )
53 | result = console.file.getvalue()
54 | assert result == expected
55 |
56 |
57 | def test_rule_in_ratio_table():
58 | console = Console(width=32, file=io.StringIO(), legacy_windows=False, _environ={})
59 | table = Table(box=box.ASCII, expand=True, show_header=False)
60 | table.add_column(ratio=1)
61 | table.add_column()
62 | table.add_row("COL1", "COL2")
63 | table.add_row("COL1", Rule(style=None))
64 | table.add_row("COL1", "COL2")
65 | console.print(table)
66 | expected = dedent(
67 | """\
68 | +------------------------------+
69 | | COL1 | COL2 |
70 | | COL1 | ──── |
71 | | COL1 | COL2 |
72 | +------------------------------+
73 | """
74 | )
75 | result = console.file.getvalue()
76 | assert result == expected
77 |
--------------------------------------------------------------------------------
/tests/test_screen.py:
--------------------------------------------------------------------------------
1 | from rich.console import Console
2 | from rich.screen import Screen
3 |
4 |
5 | def test_screen():
6 | console = Console(color_system=None, width=20, height=5, legacy_windows=False)
7 | with console.capture() as capture:
8 | console.print(Screen("foo\nbar\nbaz\nfoo\nbar\nbaz\foo"))
9 | result = capture.get()
10 | print(repr(result))
11 | expected = "foo \nbar \nbaz \nfoo \nbar "
12 | assert result == expected
13 |
--------------------------------------------------------------------------------
/tests/test_spinner.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from rich.console import Console
4 | from rich.measure import Measurement
5 | from rich.rule import Rule
6 | from rich.spinner import Spinner
7 | from rich.text import Text
8 |
9 |
10 | def test_spinner_create():
11 | Spinner("dots")
12 | with pytest.raises(KeyError):
13 | Spinner("foobar")
14 |
15 |
16 | def test_spinner_render():
17 | time = 0.0
18 |
19 | def get_time():
20 | nonlocal time
21 | return time
22 |
23 | console = Console(
24 | width=80, color_system=None, force_terminal=True, get_time=get_time
25 | )
26 | console.begin_capture()
27 | spinner = Spinner("dots", "Foo")
28 | console.print(spinner)
29 | time += 80 / 1000
30 | console.print(spinner)
31 | result = console.end_capture()
32 | print(repr(result))
33 | expected = "⠋ Foo\n⠙ Foo\n"
34 | assert result == expected
35 |
36 |
37 | def test_spinner_update():
38 | time = 0.0
39 |
40 | def get_time():
41 | nonlocal time
42 | return time
43 |
44 | console = Console(width=20, force_terminal=True, get_time=get_time, _environ={})
45 | console.begin_capture()
46 | spinner = Spinner("dots")
47 | console.print(spinner)
48 |
49 | rule = Rule("Bar")
50 |
51 | spinner.update(text=rule)
52 | time += 80 / 1000
53 | console.print(spinner)
54 |
55 | result = console.end_capture()
56 | print(repr(result))
57 | expected = "⠋\n⠙ \x1b[92m─\x1b[0m\n"
58 | assert result == expected
59 |
60 |
61 | def test_rich_measure():
62 | console = Console(width=80, color_system=None, force_terminal=True)
63 | spinner = Spinner("dots", "Foo")
64 | min_width, max_width = Measurement.get(console, console.options, spinner)
65 | assert min_width == 3
66 | assert max_width == 5
67 |
68 |
69 | def test_spinner_markup():
70 | spinner = Spinner("dots", "[bold]spinning[/bold]")
71 | assert isinstance(spinner.text, Text)
72 | assert str(spinner.text) == "spinning"
73 |
--------------------------------------------------------------------------------
/tests/test_stack.py:
--------------------------------------------------------------------------------
1 | from rich._stack import Stack
2 |
3 |
4 | def test_stack():
5 | stack = Stack()
6 | stack.push("foo")
7 | stack.push("bar")
8 | assert stack.top == "bar"
9 | assert stack.pop() == "bar"
10 | assert stack.top == "foo"
11 |
--------------------------------------------------------------------------------
/tests/test_status.py:
--------------------------------------------------------------------------------
1 | from time import sleep
2 |
3 | from rich.console import Console
4 | from rich.spinner import Spinner
5 | from rich.status import Status
6 |
7 |
8 | def test_status():
9 | console = Console(
10 | color_system=None, width=80, legacy_windows=False, get_time=lambda: 0.0
11 | )
12 | status = Status("foo", console=console)
13 | assert status.console == console
14 | previous_status_renderable = status.renderable
15 | status.update(status="bar", spinner_style="red", speed=2.0)
16 |
17 | assert previous_status_renderable == status.renderable
18 | assert isinstance(status.renderable, Spinner)
19 | status.update(spinner="dots2")
20 | assert previous_status_renderable != status.renderable
21 |
22 | # TODO: Testing output is tricky with threads
23 | with status:
24 | sleep(0.2)
25 |
26 |
27 | def test_renderable():
28 | console = Console(
29 | color_system=None, width=80, legacy_windows=False, get_time=lambda: 0.0
30 | )
31 | status = Status("foo", console=console)
32 | console.begin_capture()
33 | console.print(status)
34 | assert console.end_capture() == "⠋ foo\n"
35 |
--------------------------------------------------------------------------------
/tests/test_styled.py:
--------------------------------------------------------------------------------
1 | import io
2 |
3 | from rich.console import Console
4 | from rich.measure import Measurement
5 | from rich.styled import Styled
6 |
7 |
8 | def test_styled():
9 | styled_foo = Styled("foo", "on red")
10 | console = Console(file=io.StringIO(), force_terminal=True, _environ={})
11 | assert Measurement.get(console, console.options, styled_foo) == Measurement(3, 3)
12 | console.print(styled_foo)
13 | result = console.file.getvalue()
14 | expected = "\x1b[41mfoo\x1b[0m\n"
15 | assert result == expected
16 |
--------------------------------------------------------------------------------
/tests/test_theme.py:
--------------------------------------------------------------------------------
1 | import io
2 | import os
3 | import tempfile
4 |
5 | import pytest
6 |
7 | from rich.style import Style
8 | from rich.theme import Theme, ThemeStack, ThemeStackError
9 |
10 |
11 | def test_inherit():
12 | theme = Theme({"warning": "red"})
13 | assert theme.styles["warning"] == Style(color="red")
14 | assert theme.styles["dim"] == Style(dim=True)
15 |
16 |
17 | def test_config():
18 | theme = Theme({"warning": "red"})
19 | config = theme.config
20 | assert "warning = red\n" in config
21 |
22 |
23 | def test_from_file():
24 | theme = Theme({"warning": "red"})
25 | text_file = io.StringIO()
26 | text_file.write(theme.config)
27 | text_file.seek(0)
28 |
29 | load_theme = Theme.from_file(text_file)
30 | assert theme.styles == load_theme.styles
31 |
32 |
33 | def test_read():
34 | theme = Theme({"warning": "red"})
35 | with tempfile.TemporaryDirectory("richtheme") as name:
36 | filename = os.path.join(name, "theme.cfg")
37 | with open(filename, "wt") as write_theme:
38 | write_theme.write(theme.config)
39 | load_theme = Theme.read(filename)
40 | assert theme.styles == load_theme.styles
41 |
42 |
43 | def test_theme_stack():
44 | theme = Theme({"warning": "red"})
45 | stack = ThemeStack(theme)
46 | assert stack.get("warning") == Style.parse("red")
47 | new_theme = Theme({"warning": "bold yellow"})
48 | stack.push_theme(new_theme)
49 | assert stack.get("warning") == Style.parse("bold yellow")
50 | stack.pop_theme()
51 | assert stack.get("warning") == Style.parse("red")
52 | with pytest.raises(ThemeStackError):
53 | stack.pop_theme()
54 |
--------------------------------------------------------------------------------
/tests/test_tools.py:
--------------------------------------------------------------------------------
1 | from rich._loop import loop_first, loop_last, loop_first_last
2 | from rich._ratio import ratio_distribute
3 |
4 |
5 | def test_loop_first():
6 | assert list(loop_first([])) == []
7 | iterable = loop_first(["apples", "oranges", "pears", "lemons"])
8 | assert next(iterable) == (True, "apples")
9 | assert next(iterable) == (False, "oranges")
10 | assert next(iterable) == (False, "pears")
11 | assert next(iterable) == (False, "lemons")
12 |
13 |
14 | def test_loop_last():
15 | assert list(loop_last([])) == []
16 | iterable = loop_last(["apples", "oranges", "pears", "lemons"])
17 | assert next(iterable) == (False, "apples")
18 | assert next(iterable) == (False, "oranges")
19 | assert next(iterable) == (False, "pears")
20 | assert next(iterable) == (True, "lemons")
21 |
22 |
23 | def test_loop_first_last():
24 | assert list(loop_first_last([])) == []
25 | iterable = loop_first_last(["apples", "oranges", "pears", "lemons"])
26 | assert next(iterable) == (True, False, "apples")
27 | assert next(iterable) == (False, False, "oranges")
28 | assert next(iterable) == (False, False, "pears")
29 | assert next(iterable) == (False, True, "lemons")
30 |
31 |
32 | def test_ratio_distribute():
33 | assert ratio_distribute(10, [1]) == [10]
34 | assert ratio_distribute(10, [1, 1]) == [5, 5]
35 | assert ratio_distribute(12, [1, 3]) == [3, 9]
36 | assert ratio_distribute(0, [1, 3]) == [0, 0]
37 | assert ratio_distribute(0, [1, 3], [1, 1]) == [1, 1]
38 | assert ratio_distribute(10, [1, 0]) == [10, 0]
39 |
--------------------------------------------------------------------------------
/tools/README.md:
--------------------------------------------------------------------------------
1 | # Tools
2 |
3 | These are scripts used in the development of Rich, and aren't for general use. But feel free to look around.
4 |
5 | Some ~~strikethrough~~ text.
6 |
--------------------------------------------------------------------------------
/tools/make_emoji.py:
--------------------------------------------------------------------------------
1 | try:
2 | import emoji
3 | except ImportError:
4 | print("pip install emoji")
5 | raise
6 |
7 | from emoji.unicode_codes import EMOJI_ALIAS_UNICODE
8 |
9 | emoji = {k.lower().strip(":"): v for k, v in EMOJI_ALIAS_UNICODE.items()}
10 |
11 | with open("_emoji_codes.py", "wt") as f:
12 | f.write("EMOJI=" + str(emoji))
13 |
--------------------------------------------------------------------------------
/tools/make_terminal_widths.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | from typing import List, Tuple
3 | import sys
4 |
5 | from rich.progress import Progress
6 |
7 | from wcwidth import wcwidth
8 |
9 |
10 | progress = Progress()
11 |
12 |
13 | def make_widths_table() -> List[Tuple[int, int, int]]:
14 | table: List[Tuple[int, int, int]] = []
15 | append = table.append
16 |
17 | make_table_task = progress.add_task("Calculating table...")
18 |
19 | widths = (
20 | (codepoint, wcwidth(chr(codepoint)))
21 | for codepoint in range(0, sys.maxunicode + 1)
22 | )
23 |
24 | _widths = [(codepoint, width) for codepoint, width in widths if width != 1]
25 | iter_widths = iter(_widths)
26 |
27 | endpoint, group_cell_size = next(iter_widths)
28 | start_codepoint = end_codepoint = endpoint
29 | for codepoint, cell_size in progress.track(
30 | iter_widths, task_id=make_table_task, total=len(_widths) - 1
31 | ):
32 | if cell_size != group_cell_size or codepoint != end_codepoint + 1:
33 | append((start_codepoint, end_codepoint, group_cell_size))
34 | start_codepoint = end_codepoint = codepoint
35 | group_cell_size = cell_size
36 | else:
37 | end_codepoint = codepoint
38 | append((start_codepoint, end_codepoint, group_cell_size))
39 | return table
40 |
41 |
42 | def get_cell_size(table: List[Tuple[int, int, int]], character: str) -> int:
43 | codepoint = ord(character)
44 | lower_bound = 0
45 | upper_bound = len(table) - 1
46 | index = (lower_bound + upper_bound) // 2
47 | while True:
48 | start, end, width = table[index]
49 | if codepoint < start:
50 | upper_bound = index - 1
51 | elif codepoint > end:
52 | lower_bound = index + 1
53 | else:
54 | return width
55 | if upper_bound < lower_bound:
56 | break
57 | index = (lower_bound + upper_bound) // 2
58 | return 1
59 |
60 |
61 | def test(widths_table):
62 | for codepoint in progress.track(
63 | range(0, sys.maxunicode + 1), description="Testing..."
64 | ):
65 | character = chr(codepoint)
66 | width1 = get_cell_size(widths_table, character)
67 | width2 = wcwidth(character)
68 | if width1 != width2:
69 | print(f"{width1} != {width2}")
70 | break
71 |
72 |
73 | def run():
74 | with progress:
75 | widths_table = make_widths_table()
76 | test(widths_table)
77 | table_file = f"""# Auto generated by make_terminal_widths.py
78 |
79 | CELL_WIDTHS = {widths_table!r}
80 |
81 | """
82 | with open("../rich/_cell_widths.py", "wt") as fh:
83 | fh.write(table_file)
84 |
85 | subprocess.run("black ../rich/_cell_widths.py", shell=True)
86 |
87 |
88 | if __name__ == "__main__":
89 | run()
90 |
--------------------------------------------------------------------------------
/tools/movies.md:
--------------------------------------------------------------------------------
1 | # Top 80's Movies
2 |
3 | The 1980's was an era that produced some of the most iconic movies of all time. This page provides a brief overview of some of the most popular movies from this decade.
4 |
5 | ## Box Office Hits
6 |
7 | Here are the top 5 highest-grossing films of the 1980s:
8 |
9 | | Year | Title | Director | Box Office (USD) |
10 | |------|:----------------------------------------------:|:------------------|------------------:|
11 | | 1982 | *E.T. the Extra-Terrestrial* | Steven Spielberg | $792.9 million |
12 | | 1980 | Star Wars: Episode V – The Empire Strikes Back | Irvin Kershner | $538.4 million |
13 | | 1983 | Star Wars: Episode VI – Return of the Jedi | Richard Marquand | $475.1 million |
14 | | 1981 | Raiders of the Lost Ark | Steven Spielberg | $389.9 million |
15 | | 1984 | Indiana Jones and the Temple of Doom | Steven Spielberg | $333.1 million |
16 |
17 | ## Oscar Winners
18 |
19 | The following table displays the films from the '80s that won the Oscar for Best Picture:
20 |
21 | | Year | Movie | Director |
22 | |------|---------------------|-----------------------|
23 | | 1980 | Ordinary People | Robert Redford |
24 | | 1981 | Chariots of Fire | Hugh Hudson |
25 | | 1982 | Gandhi | Richard Attenborough |
26 | | 1983 | Terms of Endearment | James L. Brooks |
27 | | 1984 | Amadeus | Milos Forman |
28 | | 1985 | Out of Africa | Sydney Pollack |
29 | | 1986 | Platoon | Oliver Stone |
30 | | 1987 | The Last Emperor | Bernardo Bertolucci |
31 | | 1988 | Rain Man | Barry Levinson |
32 | | 1989 | Driving Miss Daisy | Bruce Beresford |
33 |
34 | ## Cult Classics
35 |
36 | The 80s also produced a number of cult classics. Here are a few notable ones:
37 |
38 | | Year | Movie | Director |
39 | |------|--------------------|-----------------|
40 | | 1985 | The Goonies | Richard Donner |
41 | | 1988 | Beetlejuice | Tim Burton |
42 | | 1984 | Ghostbusters | Ivan Reitman |
43 | | 1985 | Back to the Future | Robert Zemeckis |
44 | | 1987 | The Princess Bride | Rob Reiner |
45 |
--------------------------------------------------------------------------------
/tools/profile_divide.py:
--------------------------------------------------------------------------------
1 | from rich.segment import Segment
2 |
3 | text = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."""
4 |
5 |
6 | segments = [Segment(text[n : n + 7]) for n in range(0, len(text), 7)]
7 |
8 |
9 | from time import perf_counter
10 |
11 | start = perf_counter()
12 | for _ in range(10000):
13 | list(Segment.divide(segments, [0, 1, 20, 24, 65, len(text)]))
14 | print(perf_counter() - start)
15 |
--------------------------------------------------------------------------------
/tools/profile_pretty.py:
--------------------------------------------------------------------------------
1 | import json
2 | import io
3 | from time import time
4 | from rich.console import Console
5 | from rich.pretty import Pretty
6 |
7 |
8 | console = Console(file=io.StringIO(), color_system="truecolor", width=100)
9 |
10 | with open("cats.json") as fh:
11 | cats = json.load(fh)
12 |
13 |
14 | console.begin_capture()
15 | start = time()
16 | pretty = Pretty(cats)
17 | console.print(pretty, overflow="ignore", crop=False)
18 | result = console.end_capture()
19 | taken = (time() - start) * 1000
20 | print(result)
21 |
22 | print(console.file.getvalue())
23 | print(f"{taken:.1f}")
24 |
--------------------------------------------------------------------------------
/tools/stress_test_pretty.py:
--------------------------------------------------------------------------------
1 | from rich.console import Console
2 | from rich.panel import Panel
3 | from rich.pretty import Pretty
4 | from rich._timer import timer
5 |
6 | DATA = {
7 | "foo": [1, 2, 3, (), {}, (1, 2, 3), {4, 5, 6, (7, 8, 9)}, "Hello, World"],
8 | "bar": [None, (False, True)] * 2,
9 | "Dune": {
10 | "names": {
11 | "Paul Atreides",
12 | "Vladimir Harkonnen",
13 | "Thufir Hawat",
14 | "Duncan Idaho",
15 | }
16 | },
17 | }
18 | console = Console()
19 | with timer("Stress test"):
20 | for w in range(130):
21 | console.print(Panel(Pretty(DATA, indent_guides=True), width=w))
22 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | minversion = 4.0.0
3 | envlist =
4 | lint
5 | docs
6 | py{38,39,310,311,312,313}
7 | isolated_build = True
8 |
9 | [testenv]
10 | description = Run unit-testing
11 | deps =
12 | poetry
13 | # do not put * in passenv as it may break builds due to reduced isolation
14 | passenv =
15 | CI
16 | GITHUB_*
17 | HOME
18 | PYTEST_*
19 | SSH_AUTH_SOCK
20 | TERM
21 | setenv =
22 | PYTHONDONTWRITEBYTECODE=1
23 | PYTHONUNBUFFERED=1
24 | commands =
25 | poetry install
26 | pytest --cov-report term-missing --cov=rich tests/ {posargs}
27 |
28 | [testenv:lint]
29 | description = Runs all linting tasks
30 | commands_pre =
31 | poetry install -vv --with lint
32 | commands =
33 | ; poetry install --only dev
34 | # as long GHA pipelines are not configured to use tox, we should call
35 | # `make` in order to make testing similar and prevent divergence.
36 | make format-check
37 | make typecheck
38 | deps =
39 | poetry
40 | skip_install = true
41 | allowlist_externals =
42 | make
43 | poetry
44 |
45 | [testenv:docs]
46 | description = Builds documentation
47 | changedir = docs
48 | deps =
49 | -r docs/requirements.txt
50 | commands =
51 | sphinx-build -M html source build
52 |
--------------------------------------------------------------------------------