mirror of
https://github.com/Textualize/rich.git
synced 2025-07-07 21:04:58 +00:00
docs
This commit is contained in:
parent
c9ce00abe0
commit
e778af6aa2
31 changed files with 576 additions and 204 deletions
|
@ -4,5 +4,6 @@ Appendix
|
|||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
appendix/box.rst
|
||||
appendix/colors.rst
|
||||
|
121
docs/source/appendix/box.rst
Normal file
121
docs/source/appendix/box.rst
Normal file
|
@ -0,0 +1,121 @@
|
|||
.. _appendix-box:
|
||||
|
||||
Box
|
||||
===
|
||||
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<pre style="font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="color: #800080"> box.ASCII </span>
|
||||
+-------------------------+
|
||||
|<span style="color: #7f7f7f; font-weight: bold"> </span>|<span style="color: #7f7f7f; font-weight: bold"> </span>|
|
||||
|------------+------------|
|
||||
|<span style="color: #7f7f7f"> </span>|<span style="color: #7f7f7f"> </span>|
|
||||
|------------+------------|
|
||||
|<span style="color: #7f7f7f; font-weight: bold"> </span>|<span style="color: #7f7f7f; font-weight: bold"> </span>|
|
||||
+-------------------------+
|
||||
<span style="color: #800080"> box.SQUARE </span>
|
||||
┌────────────┬────────────┐
|
||||
│<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>│
|
||||
├────────────┼────────────┤
|
||||
│<span style="color: #7f7f7f"> </span>│<span style="color: #7f7f7f"> </span>│
|
||||
├────────────┼────────────┤
|
||||
│<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>│
|
||||
└────────────┴────────────┘
|
||||
<span style="color: #800080"> box.MINIMAL </span>
|
||||
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
────────────┼────────────
|
||||
<span style="color: #7f7f7f"> </span>│<span style="color: #7f7f7f"> </span>
|
||||
────────────┼────────────
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
|
||||
<span style="color: #800080"> box.MINIMAL_HEAVY_HEAD </span>
|
||||
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
━━━━━━━━━━━━┿━━━━━━━━━━━━
|
||||
<span style="color: #7f7f7f"> </span>│<span style="color: #7f7f7f"> </span>
|
||||
────────────┼────────────
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
|
||||
<span style="color: #800080"> box.MINIMAL_DOUBLE_HEAD </span>
|
||||
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
════════════╪════════════
|
||||
<span style="color: #7f7f7f"> </span>│<span style="color: #7f7f7f"> </span>
|
||||
────────────┼────────────
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
|
||||
<span style="color: #800080"> box.SIMPLE </span>
|
||||
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span> <span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
───────────────────────────
|
||||
<span style="color: #7f7f7f"> </span> <span style="color: #7f7f7f"> </span>
|
||||
───────────────────────────
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span> <span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
|
||||
<span style="color: #800080"> box.SIMPLE_HEAVY </span>
|
||||
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span> <span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
╺━━━━━━━━━━━━━━━━━━━━━━━━━╸
|
||||
<span style="color: #7f7f7f"> </span> <span style="color: #7f7f7f"> </span>
|
||||
╺━━━━━━━━━━━━━━━━━━━━━━━━━╸
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span> <span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
|
||||
<span style="color: #800080"> box.HORIZONTALS </span>
|
||||
───────────────────────────
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span> <span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
───────────────────────────
|
||||
<span style="color: #7f7f7f"> </span> <span style="color: #7f7f7f"> </span>
|
||||
───────────────────────────
|
||||
<span style="color: #7f7f7f; font-weight: bold"> </span> <span style="color: #7f7f7f; font-weight: bold"> </span>
|
||||
───────────────────────────
|
||||
<span style="color: #800080"> box.ROUNDED </span>
|
||||
╭────────────┬────────────╮
|
||||
│<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>│
|
||||
├────────────┼────────────┤
|
||||
│<span style="color: #7f7f7f"> </span>│<span style="color: #7f7f7f"> </span>│
|
||||
├────────────┼────────────┤
|
||||
│<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>│
|
||||
╰────────────┴────────────╯
|
||||
<span style="color: #800080"> box.HEAVY </span>
|
||||
┏━━━━━━━━━━━━┳━━━━━━━━━━━━┓
|
||||
┃<span style="color: #7f7f7f; font-weight: bold"> </span>┃<span style="color: #7f7f7f; font-weight: bold"> </span>┃
|
||||
┣━━━━━━━━━━━━╋━━━━━━━━━━━━┫
|
||||
┃<span style="color: #7f7f7f"> </span>┃<span style="color: #7f7f7f"> </span>┃
|
||||
┣━━━━━━━━━━━━╋━━━━━━━━━━━━┫
|
||||
┃<span style="color: #7f7f7f; font-weight: bold"> </span>┃<span style="color: #7f7f7f; font-weight: bold"> </span>┃
|
||||
┗━━━━━━━━━━━━┻━━━━━━━━━━━━┛
|
||||
<span style="color: #800080"> box.HEAVY_EDGE </span>
|
||||
┏━━━━━━━━━━━━┯━━━━━━━━━━━━┓
|
||||
┃<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>┃
|
||||
┠────────────┼────────────┨
|
||||
┃<span style="color: #7f7f7f"> </span>│<span style="color: #7f7f7f"> </span>┃
|
||||
┠────────────┼────────────┨
|
||||
┃<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>┃
|
||||
┗━━━━━━━━━━━━┷━━━━━━━━━━━━┛
|
||||
<span style="color: #800080"> box.HEAVY_HEAD </span>
|
||||
┏━━━━━━━━━━━━┳━━━━━━━━━━━━┓
|
||||
┃<span style="color: #7f7f7f; font-weight: bold"> </span>┃<span style="color: #7f7f7f; font-weight: bold"> </span>┃
|
||||
┡━━━━━━━━━━━━╇━━━━━━━━━━━━┩
|
||||
│<span style="color: #7f7f7f"> </span>│<span style="color: #7f7f7f"> </span>│
|
||||
├────────────┼────────────┤
|
||||
│<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>│
|
||||
└────────────┴────────────┘
|
||||
<span style="color: #800080"> box.DOUBLE </span>
|
||||
╔════════════╦════════════╗
|
||||
║<span style="color: #7f7f7f; font-weight: bold"> </span>║<span style="color: #7f7f7f; font-weight: bold"> </span>║
|
||||
╠════════════╬════════════╣
|
||||
║<span style="color: #7f7f7f"> </span>║<span style="color: #7f7f7f"> </span>║
|
||||
╠════════════╬════════════╣
|
||||
║<span style="color: #7f7f7f; font-weight: bold"> </span>║<span style="color: #7f7f7f; font-weight: bold"> </span>║
|
||||
╚════════════╩════════════╝
|
||||
<span style="color: #800080"> box.DOUBLE_EDGE </span>
|
||||
╔════════════╤════════════╗
|
||||
║<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>║
|
||||
╟────────────┼────────────╢
|
||||
║<span style="color: #7f7f7f"> </span>│<span style="color: #7f7f7f"> </span>║
|
||||
╟────────────┼────────────╢
|
||||
║<span style="color: #7f7f7f; font-weight: bold"> </span>│<span style="color: #7f7f7f; font-weight: bold"> </span>║
|
||||
╚════════════╧════════════╝
|
||||
</pre>
|
|
@ -1,11 +1,13 @@
|
|||
Console API
|
||||
===========
|
||||
|
||||
For more finely grained control over terminal formatting, Rich offers a :class:`~rich.console.Console` class. Most applications will require a single Console instance, so you may want to create one at the module level or as an attribute of your top-level object.
|
||||
For complete control over terminal formatting, Rich offers a :class:`~rich.console.Console` class. Most applications will require a single Console instance, so you may want to create one at the module level or as an attribute of your top-level object. For example::
|
||||
|
||||
from rich.console import Console
|
||||
console = Console()
|
||||
|
||||
The console object handles the mechanics of generating ANSI escape sequences for color and style. It will auto-detect the capabilities of the terminal and convert colors if necessary.
|
||||
|
||||
|
||||
Attributes
|
||||
----------
|
||||
|
@ -15,9 +17,44 @@ The console will auto-detect a number of properties required when rendering.
|
|||
* :obj:`~rich.console.Console.size` is the current dimensions of the terminal (which may change if you resize the window).
|
||||
* :obj:`~rich.console.Console.encoding` is the default encoding (typically "utf-8").
|
||||
* :obj:`~rich.console.Console.is_terminal` is a boolean that indicates if the Console instance is writing to a terminal or not.
|
||||
* :obj:`~rich.console.Console.color_system` is a string containing "standard", "256" or "truecolor", or `None` if not writing to a terminal.
|
||||
* :obj:`~rich.console.Console.color_system` is a string containing "standard", "256" or "truecolor", or ``None`` if not writing to a terminal.
|
||||
|
||||
|
||||
Printing
|
||||
--------
|
||||
|
||||
To write rich content to the terminal use the :meth:`~rich.console.Console.print` method. Rich will convert any object to a string via its (``__str__``) method and perform some simple syntax highlighting. It will also do pretty printing of any containers, such as dicts and lists. If you print a string it will render :ref:`console_markup`. Here are some examples::
|
||||
|
||||
console.print([1, 2, 3])
|
||||
console.print("[blue underline]Looks like a link")
|
||||
console.print(locals())
|
||||
|
||||
You can also use :meth:`~rich.console.Console.print` to render objects that support the :ref:`protocol`, which includes Rich's built in objects such as :class:`~rich.text.Text`, :class:`~rich.table.Table`, and :class:`~rich.syntax.Syntax` -- or other custom objects.
|
||||
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
The :meth:`~rich.console.Console.log` methods offers the same capabilities as print, but adds some features useful for debugging a running application. Logging writes the current time in a column to the left, and the file and line where the method was called to a column on the right. Here's an example::
|
||||
|
||||
>>> console.log("Hello, World!")
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<pre style="font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="color: #7fbfbf">[16:32:08] </span>Hello, World! <span style="color: #7f7f7f"><stdin>:1</span>
|
||||
</pre>
|
||||
|
||||
To help with debugging, the log() method has a ``log_locals`` parameter. If you set this to ``True`` Rich will display a table of local variables where the method was called.
|
||||
|
||||
|
||||
Exporting
|
||||
---------
|
||||
|
||||
The Console class can export anything written to it as either text or html. To enable exporting, first set ``record=True`` on the constructor. This tells Rich to save a copy of any data you ``print()`` or ``log()``. Here's an example::
|
||||
|
||||
from rich.console import Console
|
||||
console = Console(record=True)
|
||||
|
||||
After you have written content, you can call :meth:`~rich.console.Console.export_text` or :meth:`~rich.console.Console.export_html` to get the console output as a string. You can also call :meth:`~rich.console.Console.save_text` or :meth:`~rich.console.Console.save_html` to write the contents directly to disk.
|
||||
|
||||
For examples of the html output generated by Rich Console, see :ref:`appendix-colors`.
|
||||
|
|
|
@ -14,6 +14,8 @@ Welcome to Rich's documentation!
|
|||
console.rst
|
||||
markup.rst
|
||||
style.rst
|
||||
tables.rst
|
||||
protocol.rst
|
||||
|
||||
reference.rst
|
||||
appendix.rst
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
|
||||
.. _protocol:
|
||||
|
||||
Console Protocol
|
||||
================
|
||||
|
||||
Rich supports a simple protocol to add rich formatting capabilities to custom objects, so you can :meth:`~rich.console.Console.print` your object with color, styles and formatting.
|
||||
|
||||
You can use this for presentation or to display additional debugging information that might be hard to parse from a ``__repr__`` output.
|
||||
You can use this for presentation or to display additional debugging information that might be hard to parse from a typical ``__repr__`` string.
|
||||
|
||||
|
||||
Console Str
|
||||
|
@ -21,17 +24,45 @@ If you were to print or log an instance of ``MyObject`` it would render as ``MyO
|
|||
Console Render
|
||||
--------------
|
||||
|
||||
The ``__console_str__`` method is limited to styled text. For more advanced rendering, Rich supports a ``__console__`` method which you can use to render custom output with other renderable objects. For instance, a complex data type might be best represented as a :class:`~rich.table.Table`.
|
||||
The ``__console_str__`` method is limited to styled text. For more advanced rendering, Rich supports a ``__console__`` method which you can use to generate custom output with other renderable objects. For instance, a complex data type might be best represented as a :class:`~rich.table.Table`.
|
||||
|
||||
The ``__console__`` method should accept a :class:`~rich.console.Console` and a :class:`~rich.console.ConsoleOptions` instance. It should return an iterable of other renderable objects. Although that means it *could* return a container such as a list, it is customary to ``yield`` output (making the method a generator)
|
||||
|
||||
Here's an example of a ``__console__`` method::
|
||||
|
||||
class MyObject:
|
||||
@dataclass
|
||||
class Student:
|
||||
name: str
|
||||
age: int
|
||||
def __console__(self, console: Console, options: ConsoleOptions) -> Iterable[Table]:
|
||||
my_table = Table("Key, "Value")
|
||||
my_table.add_row("foo", "bar")
|
||||
my_table.add_row("egg", "baz")
|
||||
my_table = Table("Attribute, "Value")
|
||||
my_table.add_row("name", self.name)
|
||||
my_table.add_row("age", str(self.age))
|
||||
yield my_table
|
||||
|
||||
If you were to print a ``MyObject`` instance, it would render a simple table to the terminal.
|
||||
If you were to print a ``Student`` instance, it would render a simple table to the terminal.
|
||||
|
||||
|
||||
Low Level Render
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
For complete control over how a custom object is rendered to the terminal, you can yield :class:`~rich.segment.Segment` objects. A Segment consists of a piece of text and an optional Style. The following example, writes multi-colored text when rendering a ``MyObject`` instance::
|
||||
|
||||
|
||||
class MyObject:
|
||||
def __console__(self, console: Console, options: ConsoleOptions) -> Iterable[Table]:
|
||||
yield Segment("My", "magenta")
|
||||
yield Segment("Object", green")
|
||||
yield Segment("()", "cyan")
|
||||
|
||||
|
||||
Console Width
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Sometimes Rich needs to know how many characters an object will take up when rendering. The :class:`~rich.table.Table` class for instance, will use this information to calculate the optimal dimensions for the columns. If you aren't using one of the standard classes, you will need to supply a ``__console_width__`` method which accepts the maximum width as an integer and returns a :class:`~rich.render_width.RenderWidth` object. The RenderWidth object should contain the *minimum* and *maximum* number of characters required to render.
|
||||
|
||||
For example, if we are rendering a chess board, it would require a minimum of 8 characters to render. The maximum can be left as the maximum available width (assuming a centered board)::
|
||||
|
||||
class ChessBoard:
|
||||
def __console_width__(self, max_width: int) -> RenderWidth:
|
||||
return RenderWidth(8, max_width)
|
||||
|
|
|
@ -8,5 +8,9 @@ Reference
|
|||
reference/console.rst
|
||||
reference/emoji.rst
|
||||
reference/highlighter.rst
|
||||
reference/render_width.rst
|
||||
reference/panel.rst
|
||||
reference/segment.rst
|
||||
reference/style.rst
|
||||
reference/table.rst
|
||||
reference/text.rst
|
||||
|
|
6
docs/source/reference/panel.rst
Normal file
6
docs/source/reference/panel.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
rich.panel
|
||||
==========
|
||||
|
||||
.. automodule:: rich.panel
|
||||
:members: Panel
|
||||
|
5
docs/source/reference/render_width.rst
Normal file
5
docs/source/reference/render_width.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
rich.render_width
|
||||
=================
|
||||
|
||||
.. automodule:: rich.render_width
|
||||
:members:
|
5
docs/source/reference/segment.rst
Normal file
5
docs/source/reference/segment.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
rich.segment
|
||||
============
|
||||
|
||||
.. automodule:: rich.segment
|
||||
:members:
|
6
docs/source/reference/table.rst
Normal file
6
docs/source/reference/table.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
rich.table
|
||||
==========
|
||||
|
||||
.. automodule:: rich.table
|
||||
:members: Table
|
||||
|
45
docs/source/tables.rst
Normal file
45
docs/source/tables.rst
Normal file
|
@ -0,0 +1,45 @@
|
|||
Tables
|
||||
======
|
||||
|
||||
Rich's :class:`~rich.table.Table` class offers a variety ways of rendering tabular data to the terminal.
|
||||
|
||||
To render a table, construct a :class:`~rich.table.Table` object, add data, then call :meth:`~rich.console.Console.print` or :meth:`~rich.console.Console.log` to write it to the console.
|
||||
|
||||
Here's an example::
|
||||
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
table = Table(title="Star Wars Movies")
|
||||
|
||||
table.add_column("Released", justify="right", style="cyan", no_wrap=True)
|
||||
table.add_column("Title", style="magenta")
|
||||
table.add_column("Box Office", justify="right", style="green")
|
||||
|
||||
table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690")
|
||||
table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
|
||||
table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889")
|
||||
table.add_row("Dec 16, 2016", "Rouge One: A Star Wars Story", "$1,332,439,889")
|
||||
|
||||
console = Console()
|
||||
console.print(table)
|
||||
|
||||
This produces the following output:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<pre style="font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="font-style: italic"> Star Wars Movies </span>
|
||||
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
|
||||
┃<span style="font-weight: bold"> Released </span>┃<span style="font-weight: bold"> Title </span>┃<span style="font-weight: bold"> Box Office </span>┃
|
||||
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
|
||||
│<span style="color: #008080"> Dec 20, 2019 </span>│<span style="color: #800080"> Star Wars: The Rise of Skywalker </span>│<span style="color: #008000"> $952,110,690 </span>│
|
||||
│<span style="color: #008080"> May 25, 2018 </span>│<span style="color: #800080"> Solo: A Star Wars Story </span>│<span style="color: #008000"> $393,151,347 </span>│
|
||||
│<span style="color: #008080"> Dec 15, 2017 </span>│<span style="color: #800080"> Star Wars Ep. V111: The Last Jedi </span>│<span style="color: #008000"> $1,332,539,889 </span>│
|
||||
│<span style="color: #008080"> Dec 16, 2016 </span>│<span style="color: #800080"> Rouge One: A Star Wars Story </span>│<span style="color: #008000"> $1,332,439,889 </span>│
|
||||
└──────────────┴───────────────────────────────────┴────────────────┘
|
||||
</pre>
|
||||
|
||||
|
||||
Rich is quite smart about rendering the table. It will adjust the columns widths to fit the contents and will wrap text if it doesn't fit. You can also add anything that Rich knows how to render as a title or row cell (even another table)!
|
||||
|
||||
The :class:`~rich.table.Table` class offers a number of configuration options to set the look and feel of the table, including how the borders are rendered, and the style and alignment of the columns.
|
|
@ -25,18 +25,18 @@ console.push_styles(
|
|||
)
|
||||
|
||||
console.log("Server starting...")
|
||||
console.log("Serving on http://127.0.0.1:8000", highlight=None)
|
||||
console.log("Serving on http://127.0.0.1:8000")
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
console.log(
|
||||
"HTTP GET /foo/bar/baz/egg.html 200 [0.57, 127.0.0.1:59076]",
|
||||
highlight=RequestHighlighter(),
|
||||
highlighter=RequestHighlighter(),
|
||||
)
|
||||
|
||||
console.log(
|
||||
"HTTP GET /foo/bar/baz/background.jpg 200 [0.57, 127.0.0.1:59076]",
|
||||
highlight=RequestHighlighter(),
|
||||
highlighter=RequestHighlighter(),
|
||||
)
|
||||
|
||||
|
||||
|
|
26
examples/table.html
Normal file
26
examples/table.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<style>
|
||||
|
||||
body {
|
||||
color: #000000;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<html>
|
||||
<body>
|
||||
<code>
|
||||
<pre style="font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="font-style: italic"> Star Wars Movies </span>
|
||||
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
|
||||
┃<span style="font-weight: bold"> Released </span>┃<span style="font-weight: bold"> Title </span>┃<span style="font-weight: bold"> Box Office </span>┃
|
||||
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
|
||||
│<span style="color: #008080"> Dec 20, 2019 </span>│<span style="color: #800080"> Star Wars: The Rise of Skywalker </span>│<span style="color: #008000"> $952,110,690 </span>│
|
||||
│<span style="color: #008080"> May 25, 2018 </span>│<span style="color: #800080"> Solo: A Star Wars Story </span>│<span style="color: #008000"> $393,151,347 </span>│
|
||||
│<span style="color: #008080"> Dec 15, 2017 </span>│<span style="color: #800080"> Star Wars Ep. V111: The Last Jedi </span>│<span style="color: #008000"> $1,332,539,889 </span>│
|
||||
│<span style="color: #008080"> Dec 16, 2016 </span>│<span style="color: #800080"> Rouge One: A Star Wars Story </span>│<span style="color: #008000"> $1,332,439,889 </span>│
|
||||
└──────────────┴───────────────────────────────────┴────────────────┘
|
||||
</pre>
|
||||
</code>
|
||||
</body>
|
||||
</html>
|
17
examples/table.py
Normal file
17
examples/table.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
table = Table(title="Star Wars Movies")
|
||||
|
||||
table.add_column("Released", justify="right", style="cyan", no_wrap=True)
|
||||
table.add_column("Title", style="magenta")
|
||||
table.add_column("Box Office", justify="right", style="green")
|
||||
|
||||
table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690")
|
||||
table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
|
||||
table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889")
|
||||
table.add_row("Dec 16, 2016", "Rouge One: A Star Wars Story", "$1,332,439,889")
|
||||
|
||||
console = Console(record=True)
|
||||
console.print(table)
|
||||
console.save_html("table.html", inline_styles=True)
|
55
rich/box.py
55
rich/box.py
|
@ -75,7 +75,6 @@ class Box:
|
|||
if not last:
|
||||
append(self.top_divider)
|
||||
append(self.top_right)
|
||||
append("\n")
|
||||
return "".join(parts)
|
||||
|
||||
def get_row(
|
||||
|
@ -120,7 +119,6 @@ class Box:
|
|||
append(cross)
|
||||
if edge:
|
||||
append(right)
|
||||
append("\n")
|
||||
return "".join(parts)
|
||||
|
||||
def get_bottom(self, widths: Iterable[int]) -> str:
|
||||
|
@ -141,7 +139,6 @@ class Box:
|
|||
if not last:
|
||||
append(self.bottom_divider)
|
||||
append(self.bottom_right)
|
||||
append("\n")
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
|
@ -334,29 +331,43 @@ DOUBLE_EDGE = Box(
|
|||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
print("ASCII")
|
||||
print(ASCII)
|
||||
|
||||
print("SQUARE")
|
||||
print(SQUARE)
|
||||
from .console import Console
|
||||
from .panel import Panel
|
||||
from .table import Table
|
||||
from .text import Text
|
||||
from . import box
|
||||
|
||||
print("HORIZONTALS")
|
||||
print(HORIZONTALS)
|
||||
import sys
|
||||
|
||||
print("ROUNDED")
|
||||
print(ROUNDED)
|
||||
console = Console(record=True)
|
||||
|
||||
print("HEAVY")
|
||||
print(HEAVY)
|
||||
table = Table(width=80, show_footer=True, style="dim", border_style="not dim")
|
||||
spaces = " " * 10
|
||||
table.add_column(spaces, spaces)
|
||||
table.add_column(spaces, spaces)
|
||||
table.add_row(spaces, spaces)
|
||||
|
||||
print("HEAVY_EDGE")
|
||||
print(HEAVY_EDGE)
|
||||
BOXES = [
|
||||
"ASCII",
|
||||
"SQUARE",
|
||||
"MINIMAL",
|
||||
"MINIMAL_HEAVY_HEAD",
|
||||
"MINIMAL_DOUBLE_HEAD",
|
||||
"SIMPLE",
|
||||
"SIMPLE_HEAVY",
|
||||
"HORIZONTALS",
|
||||
"ROUNDED",
|
||||
"HEAVY",
|
||||
"HEAVY_EDGE",
|
||||
"HEAVY_HEAD",
|
||||
"DOUBLE",
|
||||
"DOUBLE_EDGE",
|
||||
]
|
||||
|
||||
print("HEAVY_HEAD")
|
||||
print(HEAVY_HEAD)
|
||||
for box_name in BOXES:
|
||||
table.box = getattr(box, box_name)
|
||||
table.title = Text(f"box.{box_name}", style="magenta")
|
||||
console.print(table)
|
||||
|
||||
print("DOUBLE")
|
||||
print(DOUBLE)
|
||||
|
||||
print("DOUBLE_EDGE")
|
||||
print(DOUBLE_EDGE)
|
||||
console.save_html("box.html", inline_styles=True)
|
||||
|
|
|
@ -30,12 +30,12 @@ from typing_extensions import Protocol, runtime_checkable, Literal
|
|||
from ._emoji_replace import _emoji_replace
|
||||
|
||||
from . import markup
|
||||
from ._render_width import RenderWidth
|
||||
from .render_width import RenderWidth
|
||||
from ._log_render import LogRender
|
||||
from .default_styles import DEFAULT_STYLES
|
||||
from . import errors
|
||||
from .color import ColorSystem
|
||||
from .highlighter import ReprHighlighter
|
||||
from .highlighter import NullHighlighter, ReprHighlighter
|
||||
from .pretty import Pretty
|
||||
from .style import Style
|
||||
from .tabulate import tabulate_mapping
|
||||
|
@ -170,6 +170,7 @@ class Console:
|
|||
log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True.
|
||||
log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True.
|
||||
log_time_format (str, optional): Log time format if ``log_time`` is enabled. Defaults to "[%X] ".
|
||||
highlighter(HighlighterType, optional): Default highlighter.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
@ -186,21 +187,8 @@ class Console:
|
|||
log_time: bool = True,
|
||||
log_path: bool = True,
|
||||
log_time_format: str = "[%X] ",
|
||||
highlighter: "HighlighterType" = ReprHighlighter(),
|
||||
):
|
||||
"""[summary]
|
||||
|
||||
Args:
|
||||
color_system (Optional[Literal[, optional): [description]. Defaults to "auto".
|
||||
styles (Dict[str, Style], optional): [description]. Defaults to None.
|
||||
file (IO, optional): [description]. Defaults to None.
|
||||
width (int, optional): [description]. Defaults to None.
|
||||
height (int, optional): [description]. Defaults to None.
|
||||
record (bool, optional): [description]. Defaults to False.
|
||||
markup (bool, optional): [description]. Defaults to True.
|
||||
log_time (bool, optional): [description]. Defaults to True.
|
||||
log_path (bool, optional): [description]. Defaults to True.
|
||||
log_time_format (str, optional): [description]. Defaults to "[%X] ".
|
||||
"""
|
||||
|
||||
self._styles = ChainMap(DEFAULT_STYLES if styles is None else styles)
|
||||
self.file = file or sys.stdout
|
||||
|
@ -227,6 +215,7 @@ class Console:
|
|||
self._log_render = LogRender(
|
||||
show_time=log_time, show_path=log_path, time_format=log_time_format
|
||||
)
|
||||
self.highlighter: HighlighterType = highlighter
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<console width={self.width} {str(self._color_system)}>"
|
||||
|
@ -569,7 +558,7 @@ class Console:
|
|||
sep: str,
|
||||
end: str,
|
||||
emoji=True,
|
||||
highlight: "HighlighterType" = None,
|
||||
highlighter: "HighlighterType" = None,
|
||||
) -> List[ConsoleRenderable]:
|
||||
"""Combined a number of renderables and text in to one renderable.
|
||||
|
||||
|
@ -589,6 +578,8 @@ class Console:
|
|||
text: List[Text] = []
|
||||
append_text = text.append
|
||||
|
||||
_highlighter = highlighter or self.highlighter
|
||||
|
||||
def check_text() -> None:
|
||||
if text:
|
||||
if end:
|
||||
|
@ -610,24 +601,49 @@ class Console:
|
|||
if emoji:
|
||||
render_str = _emoji_replace(render_str)
|
||||
render_text = self.render_str(render_str)
|
||||
append_text(highlight(render_text) if highlight else render_text)
|
||||
append_text(_highlighter(render_text))
|
||||
elif isinstance(renderable, Text):
|
||||
append_text(renderable)
|
||||
elif isinstance(renderable, (int, float, bool, bytes, type(None))):
|
||||
append_text(
|
||||
highlight(repr(renderable)) if highlight else Text(repr(renderable))
|
||||
)
|
||||
append_text(_highlighter(repr(renderable)))
|
||||
elif isinstance(renderable, (Mapping, Sequence)):
|
||||
check_text()
|
||||
append(Pretty(renderable))
|
||||
append(Pretty(renderable, highlighter=_highlighter))
|
||||
else:
|
||||
append_text(
|
||||
highlight(repr(renderable)) if highlight else Text(repr(renderable))
|
||||
)
|
||||
append_text(_highlighter(repr(renderable)))
|
||||
|
||||
check_text()
|
||||
return renderables
|
||||
|
||||
def rule(self, title: str = "", character: str = "─") -> None:
|
||||
"""Draw a line with optional centered title.
|
||||
|
||||
Args:
|
||||
title (str, optional): Text to render over the rule. Defaults to "".
|
||||
character (str, optional): Character to form the line. Defaults to "─".
|
||||
"""
|
||||
|
||||
from .text import Text
|
||||
|
||||
width = self.width
|
||||
|
||||
if not title:
|
||||
self.print(Text(character * width, "rule.line"))
|
||||
else:
|
||||
title_text = Text.from_markup(title, "rule.text")
|
||||
if len(title_text) > width - 4:
|
||||
title_text.set_length(width - 4)
|
||||
|
||||
rule_text = Text()
|
||||
|
||||
center = (width - len(title_text)) // 2
|
||||
rule_text.append(character * (center - 1) + " ", "rule.line")
|
||||
rule_text.append(title_text)
|
||||
rule_text.append(
|
||||
" " + character * (width - len(rule_text) - 1), "rule.line"
|
||||
)
|
||||
self.print(rule_text)
|
||||
|
||||
def print(
|
||||
self,
|
||||
*objects: Any,
|
||||
|
@ -635,7 +651,7 @@ class Console:
|
|||
end="\n",
|
||||
style: Union[str, Style] = None,
|
||||
emoji=True,
|
||||
highlight: "HighlighterType" = None,
|
||||
highlighter: "HighlighterType" = None,
|
||||
) -> None:
|
||||
r"""Print to the console.
|
||||
|
||||
|
@ -646,7 +662,7 @@ class Console:
|
|||
end (str, optional): String to write at end of print data. Defaults to "\n".
|
||||
style (Union[str, Style], optional): A style to apply to output. Defaults to None.
|
||||
emoji (bool): If True, emoji codes will be replaced, otherwise emoji codes will be left in.
|
||||
highlight ([type], optional): A callable that accepts a :class:`~rich.text.Text` instance
|
||||
highlighter ([type], optional): A callable that accepts a :class:`~rich.text.Text` instance
|
||||
and returns a Text instance, used to add highlighting. Defaults to None.
|
||||
"""
|
||||
if not objects:
|
||||
|
@ -654,7 +670,7 @@ class Console:
|
|||
return
|
||||
|
||||
renderables = self._collect_renderables(
|
||||
objects, sep=sep, end=end, emoji=emoji, highlight=highlight
|
||||
objects, sep=sep, end=end, emoji=emoji, highlighter=highlighter,
|
||||
)
|
||||
|
||||
render_options = self.options
|
||||
|
@ -669,7 +685,7 @@ class Console:
|
|||
*objects: Any,
|
||||
sep=" ",
|
||||
end="\n",
|
||||
highlight: "HighlighterType" = None,
|
||||
highlighter: "HighlighterType" = None,
|
||||
log_locals: bool = False,
|
||||
_stack_offset=1,
|
||||
) -> None:
|
||||
|
@ -679,7 +695,7 @@ class Console:
|
|||
objects (positional args): Objects to log to the terminal.
|
||||
sep (str, optional): String to write between print data. Defaults to " ".
|
||||
end (str, optional): String to write at end of print data. Defaults to "\n".
|
||||
highlight ([type], optional): A callable that accepts a :class:`~rich.text.Text` instance
|
||||
highlighter (HighlighterType, optional): A callable that accepts a :class:`~rich.text.Text` instance
|
||||
and returns a Text instance, used to add highlighting. Defaults to None.
|
||||
log_locals (bool, optional): Boolean to enable logging of locals where ``log()``
|
||||
was called. Defaults to False.
|
||||
|
@ -688,9 +704,8 @@ class Console:
|
|||
if not objects:
|
||||
self.line()
|
||||
return
|
||||
highlighter = highlight or ReprHighlighter()
|
||||
renderables = self._collect_renderables(
|
||||
objects, sep=sep, end=end, highlight=highlighter
|
||||
objects, sep=sep, end=end, highlighter=highlighter
|
||||
)
|
||||
|
||||
caller = inspect.stack()[_stack_offset]
|
||||
|
|
|
@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
|||
)
|
||||
from .text import Text
|
||||
|
||||
from ._render_width import RenderWidth
|
||||
from .render_width import RenderWidth
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
|
@ -43,24 +43,27 @@ DEFAULT_STYLES: Dict[str, Style] = {
|
|||
"log.time": Style(color="cyan", dim=True),
|
||||
"log.message": Style(),
|
||||
"log.path": Style(dim=True),
|
||||
"repr.str": Style(color="green"),
|
||||
"repr.str": Style(color="green", italic=False),
|
||||
"repr.brace": Style(bold=True),
|
||||
"repr.tag_start": Style(bold=True),
|
||||
"repr.tag_name": Style(color="bright_magenta"),
|
||||
"repr.tag_contents": Style(color="default"),
|
||||
"repr.tag_contents": Style(color="default", italic=True),
|
||||
"repr.tag_end": Style(bold=True),
|
||||
"repr.attrib_name": Style(color="yellow", italic=True),
|
||||
"repr.attrib_equal": Style(bold=True),
|
||||
"repr.attrib_value": Style(color="magenta"),
|
||||
"repr.number": Style(color="blue", bold=True),
|
||||
"repr.attrib_value": Style(color="magenta", italic=False),
|
||||
"repr.number": Style(color="blue", bold=True, italic=False),
|
||||
"repr.bool_true": Style(color="bright_green", italic=True),
|
||||
"repr.bool_false": Style(color="bright_red", italic=True),
|
||||
"repr.none": Style(color="magenta", italic=True),
|
||||
"repr.url": Style(underline=True, color="default"),
|
||||
"rule.line": Style(color="green"),
|
||||
"rule.text": Style(),
|
||||
"table.header": Style(bold=True),
|
||||
"table.footer": Style(bold=True),
|
||||
"table.cell": Style(),
|
||||
"table.title": Style(italic=True),
|
||||
"table.caption": Style(italic=True, dim=True),
|
||||
}
|
||||
|
||||
MARKDOWN_STYLES = {
|
||||
|
|
|
@ -37,6 +37,11 @@ class Highlighter(ABC):
|
|||
"""
|
||||
|
||||
|
||||
class NullHighlighter(Highlighter):
|
||||
def highlight(self, text: Text) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class RegexHighlighter(Highlighter):
|
||||
"""Applies highlighting from a list of regular expressions."""
|
||||
|
||||
|
|
119
rich/markdown.py
119
rich/markdown.py
|
@ -428,71 +428,62 @@ class Markdown:
|
|||
new_line = element.new_line
|
||||
|
||||
|
||||
markup = """
|
||||
An h1 header
|
||||
============
|
||||
|
||||
Paragraphs are separated by a blank line.
|
||||
|
||||
2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists look like:
|
||||
|
||||
* this one
|
||||
* that one
|
||||
* the other one
|
||||
|
||||
Note that --- not considering the asterisk --- the actual text content starts at 4-columns in.
|
||||
|
||||
> Block quotes are
|
||||
> written like so.
|
||||
>
|
||||
> They can span multiple paragraphs,
|
||||
> if you like.
|
||||
|
||||
Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all in chapters 12--14"). Three dots ... will be converted to an ellipsis. Unicode is supported. ☺
|
||||
|
||||
|
||||
An h2 header
|
||||
------------
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def adjust_line_length(
|
||||
cls, line: List[Segment], length: int, style: Style = None
|
||||
) -> List[Segment]:
|
||||
line_length = sum(len(text) for text, _style in line)
|
||||
if line_length < length:
|
||||
return line[:] + [Segment(" " * (length - line_length), style)]
|
||||
elif line_length > length:
|
||||
line_length = 0
|
||||
new_line: List[Segment] = []
|
||||
append = new_line.append
|
||||
for segment in line:
|
||||
segment_length = len(segment.text)
|
||||
if line_length + segment_length < length:
|
||||
append(segment)
|
||||
line_length += segment_length
|
||||
else:
|
||||
text, style = segment
|
||||
append(Segment(text[: length - line_length], style))
|
||||
break
|
||||
return new_line
|
||||
return line
|
||||
```
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# markup = """\
|
||||
# # Heading
|
||||
|
||||
# This is `code`!
|
||||
# Hello, *World*!
|
||||
# **Bold**
|
||||
|
||||
# """
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
markup = """
|
||||
An h1 header
|
||||
============
|
||||
|
||||
Paragraphs are separated by a blank line.
|
||||
|
||||
2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists look like:
|
||||
|
||||
* this one
|
||||
* that one
|
||||
* the other one
|
||||
|
||||
Note that --- not considering the asterisk --- the actual text content starts at 4-columns in.
|
||||
|
||||
> Block quotes are
|
||||
> written like so.
|
||||
>
|
||||
> They can span multiple paragraphs,
|
||||
> if you like.
|
||||
|
||||
Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all in chapters 12--14"). Three dots ... will be converted to an ellipsis. Unicode is supported. ☺
|
||||
|
||||
|
||||
An h2 header
|
||||
------------
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def adjust_line_length(
|
||||
cls, line: List[Segment], length: int, style: Style = None
|
||||
) -> List[Segment]:
|
||||
line_length = sum(len(text) for text, _style in line)
|
||||
if line_length < length:
|
||||
return line[:] + [Segment(" " * (length - line_length), style)]
|
||||
elif line_length > length:
|
||||
line_length = 0
|
||||
new_line: List[Segment] = []
|
||||
append = new_line.append
|
||||
for segment in line:
|
||||
segment_length = len(segment.text)
|
||||
if line_length + segment_length < length:
|
||||
append(segment)
|
||||
line_length += segment_length
|
||||
else:
|
||||
text, style = segment
|
||||
append(Segment(text[: length - line_length], style))
|
||||
break
|
||||
return new_line
|
||||
return line
|
||||
```
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from .console import Console
|
||||
|
||||
console = Console(record=True, width=90)
|
||||
|
|
|
@ -7,7 +7,7 @@ if TYPE_CHECKING:
|
|||
RenderableType,
|
||||
RenderResult,
|
||||
)
|
||||
from ._render_width import RenderWidth
|
||||
from .render_width import RenderWidth
|
||||
from .style import Style
|
||||
from .text import Text
|
||||
from .segment import Segment
|
||||
|
|
|
@ -15,6 +15,20 @@ from .segment import Segment
|
|||
|
||||
|
||||
class Panel:
|
||||
"""A console renderable that draws a border around its contents.
|
||||
|
||||
Example::
|
||||
>>> console.print(Panel("Hello, World!))
|
||||
|
||||
Args:
|
||||
renderable (ConsoleRenderable): A console renderable objects.
|
||||
box (Box, optional): A Box instance that defines the look of the border.
|
||||
Defaults to box.SQUARE.
|
||||
expand (bool, optional): If True the panel will stretch to fill the console
|
||||
width, otherwise it will be sized to fit the contents. Defaults to False.
|
||||
style (str, optional): The style of the border. Defaults to "none".
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
renderable: Union[str, ConsoleRenderable],
|
||||
|
@ -22,16 +36,7 @@ class Panel:
|
|||
expand: bool = True,
|
||||
style: Union[str, Style] = "none",
|
||||
) -> None:
|
||||
"""A console renderable that draws a border around its contents.
|
||||
|
||||
Args:
|
||||
renderable (ConsoleRenderable): A console renderable objects.
|
||||
box (Box, optional): A Box instance that defines the look of the border.
|
||||
Defaults to box.SQUARE.
|
||||
expand (bool, optional): If True the panel will stretch to fill the console
|
||||
width, otherwise it will be sized to fit the contents. Defaults to False.
|
||||
style (str, optional): The style of the border. Defaults to "none".
|
||||
"""
|
||||
|
||||
self.renderable = (
|
||||
Text.from_markup(renderable) if isinstance(renderable, str) else renderable
|
||||
)
|
||||
|
@ -61,11 +66,13 @@ class Panel:
|
|||
line_start = Segment(box.mid_left, style)
|
||||
line_end = Segment(f"{box.mid_right}\n", style)
|
||||
yield Segment(box.get_top([width - 2]), style)
|
||||
yield Segment.line()
|
||||
for line in lines:
|
||||
yield line_start
|
||||
yield from line
|
||||
yield line_end
|
||||
yield Segment(box.get_bottom([width - 2]), style)
|
||||
yield Segment.line()
|
||||
|
||||
def __console_width__(self, max_width: int) -> RenderWidth:
|
||||
if self.expand:
|
||||
|
@ -74,7 +81,7 @@ class Panel:
|
|||
return RenderWidth(width, width)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from .console import Console
|
||||
|
||||
c = Console(width=22)
|
||||
|
|
|
@ -2,24 +2,24 @@ from typing import Any, TYPE_CHECKING
|
|||
|
||||
from pprintpp import pformat
|
||||
|
||||
from ._render_width import RenderWidth
|
||||
from .render_width import RenderWidth
|
||||
from .highlighter import ReprHighlighter
|
||||
from .text import Text
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from .console import Console, ConsoleOptions, RenderResult
|
||||
from .console import Console, ConsoleOptions, HighlighterType, RenderResult
|
||||
|
||||
|
||||
class Pretty:
|
||||
def __init__(self, _object: Any) -> None:
|
||||
def __init__(self, _object: Any, highlighter: "HighlighterType" = None) -> None:
|
||||
self._object = _object
|
||||
self.highlighter = highlighter or Text
|
||||
|
||||
def __console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
highlighter = ReprHighlighter()
|
||||
pretty_str = pformat(self._object, width=options.max_width)
|
||||
pretty_text = highlighter(pretty_str)
|
||||
pretty_text = self.highlighter(pretty_str)
|
||||
yield pretty_text
|
||||
|
||||
def __console_width__(self, max_width: int) -> "RenderWidth":
|
||||
|
|
|
@ -26,11 +26,11 @@ class Segment(NamedTuple):
|
|||
cls, segments: Iterable["Segment"], style: Style = None
|
||||
) -> Iterable["Segment"]:
|
||||
"""Apply a style to an iterable of segments.
|
||||
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): Segments to process.
|
||||
style (Style, optional): A style to apply. Defaults to None.
|
||||
|
||||
|
||||
Returns:
|
||||
Iterable[Segments]: A new iterable of segments (possibly the same iterable).
|
||||
"""
|
||||
|
@ -44,12 +44,12 @@ class Segment(NamedTuple):
|
|||
cls, segments: Iterable["Segment"], length: int, style: Style = None
|
||||
) -> Iterable[List["Segment"]]:
|
||||
"""Split segments in to lines, and crop lines greater than a given length.
|
||||
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): An iterable of segments, probably
|
||||
segments (Iterable[Segment]): An iterable of segments, probably
|
||||
generated from console.render.
|
||||
length (Optional[int]): Desired line length.
|
||||
|
||||
length (Optional[int]): Desired line length.
|
||||
|
||||
Returns:
|
||||
Iterable[List[Segment]]: An iterable of lines of segments.
|
||||
"""
|
||||
|
@ -77,12 +77,12 @@ class Segment(NamedTuple):
|
|||
cls, line: List["Segment"], length: int, style: Style = None
|
||||
) -> List["Segment"]:
|
||||
"""Adjust a line to a given width (cropping or padding as required.
|
||||
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): A list of segments in a single line.
|
||||
length (int): The desired width of the line.
|
||||
style (Style, optional): The style of padding if used (space on the end). Defaults to None.
|
||||
|
||||
|
||||
Returns:
|
||||
List[Segment]: A line of segments with the desired length.
|
||||
"""
|
||||
|
@ -108,10 +108,10 @@ class Segment(NamedTuple):
|
|||
@classmethod
|
||||
def get_line_length(cls, line: List["Segment"]) -> int:
|
||||
r"""Get the length of list of segments.
|
||||
|
||||
|
||||
Args:
|
||||
line (List[Segment]): A line encoded as a list of Segments (assumes no '\n' characters),
|
||||
|
||||
|
||||
Returns:
|
||||
int: The length of the line.
|
||||
"""
|
||||
|
@ -119,13 +119,13 @@ class Segment(NamedTuple):
|
|||
|
||||
@classmethod
|
||||
def get_shape(cls, lines: List[List["Segment"]]) -> Tuple[int, int]:
|
||||
"""Get the shape (enclosing rectangle) of a list of lines
|
||||
|
||||
"""Get the shape (enclosing rectangle) of a list of lines.
|
||||
|
||||
Args:
|
||||
lines (List[List[Segment]]): A list of lines (no '\n' characters)
|
||||
|
||||
lines (List[List[Segment]]): A list of lines (no '\n' characters).
|
||||
|
||||
Returns:
|
||||
Tuple[int, int]: Width and height in characters
|
||||
Tuple[int, int]: Width and height in characters.
|
||||
"""
|
||||
get_line_length = cls.get_line_length
|
||||
max_width = max(get_line_length(line) for line in lines)
|
||||
|
@ -140,13 +140,13 @@ class Segment(NamedTuple):
|
|||
style: Style = None,
|
||||
) -> List[List["Segment"]]:
|
||||
"""Set the shape of a list of lines (enclosing rectangle)
|
||||
|
||||
|
||||
Args:
|
||||
lines (List[List[Segment]]): A list of lines.
|
||||
width (int): Desired width.
|
||||
height (int, optional): Desired height or None for no change..
|
||||
style (Style, optional): Style of any padding added. Defaults to None.
|
||||
|
||||
|
||||
Returns:
|
||||
[type]: New list of lines that fits width x height.
|
||||
"""
|
||||
|
@ -166,10 +166,10 @@ class Segment(NamedTuple):
|
|||
@classmethod
|
||||
def simplify(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
|
||||
"""Simplify an iterable of segments by combining contiguous segments with the same style.
|
||||
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): An iterable segments.
|
||||
|
||||
|
||||
Returns:
|
||||
Iterable[Segment]: A possibly smaller iterable of segments that will render the same way.
|
||||
"""
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from functools import lru_cache
|
||||
import sys
|
||||
from typing import Any, Dict, Iterable, List, Mapping, Optional, Type
|
||||
from typing import Any, Dict, Iterable, List, Mapping, Optional, Type, Union
|
||||
|
||||
from . import errors
|
||||
from .color import blend_rgb, Color, ColorParseError, ColorSystem
|
||||
|
@ -124,6 +124,14 @@ class Style:
|
|||
except errors.StyleSyntaxError:
|
||||
return style.strip().lower()
|
||||
|
||||
@classmethod
|
||||
def pick_first(cls, *values: Optional[Union["Style", str]]) -> Union["Style", str]:
|
||||
"""Pick first non-None style."""
|
||||
for value in values:
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError("expected at least one non-None style")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Render a named style differently from an anonymous style."""
|
||||
return f'<style "{self}">'
|
||||
|
|
|
@ -29,7 +29,7 @@ from .padding import Padding, PaddingDimensions
|
|||
from .segment import Segment
|
||||
from .style import Style
|
||||
from .text import Text
|
||||
from ._render_width import RenderWidth
|
||||
from .render_width import RenderWidth
|
||||
from ._tools import iter_first_last, iter_first, iter_last, ratio_divide
|
||||
|
||||
|
||||
|
@ -45,6 +45,7 @@ class Column:
|
|||
justify: "JustifyValues" = "left"
|
||||
width: Optional[int] = None
|
||||
ratio: Optional[int] = None
|
||||
no_wrap: bool = False
|
||||
_cells: List["ConsoleRenderable"] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
|
@ -86,10 +87,10 @@ class Table:
|
|||
def __init__(
|
||||
self,
|
||||
*headers: Union[Column, str],
|
||||
title: str = None,
|
||||
footer: str = None,
|
||||
title: Union[str, Text] = None,
|
||||
caption: Union[str, Text] = None,
|
||||
width: int = None,
|
||||
box: Optional[box.Box] = box.DOUBLE_EDGE,
|
||||
box: Optional[box.Box] = box.HEAVY_HEAD,
|
||||
padding: PaddingDimensions = (0, 1),
|
||||
pad_edge: bool = True,
|
||||
expand: bool = False,
|
||||
|
@ -101,12 +102,14 @@ class Table:
|
|||
footer_style: Union[str, Style] = None,
|
||||
border_style: Union[str, Style] = None,
|
||||
title_style: Union[str, Style] = None,
|
||||
caption_style: Union[str, Style] = None,
|
||||
) -> None:
|
||||
self.columns = [
|
||||
(Column(header) if isinstance(header, str) else header)
|
||||
for header in headers
|
||||
]
|
||||
self.title = title
|
||||
self.caption = caption
|
||||
self.width = width
|
||||
self.box = box
|
||||
self._padding = Padding.unpack(padding)
|
||||
|
@ -120,6 +123,7 @@ class Table:
|
|||
self.footer_style = footer_style
|
||||
self.border_style = border_style
|
||||
self.title_style = title_style
|
||||
self.caption_style = title_style
|
||||
self._row_count = 0
|
||||
|
||||
@property
|
||||
|
@ -141,6 +145,7 @@ class Table:
|
|||
justify: "JustifyValues" = "left",
|
||||
width: int = None,
|
||||
ratio: int = None,
|
||||
no_wrap: bool = False,
|
||||
):
|
||||
"""Add a column to the table.
|
||||
|
||||
|
@ -155,30 +160,23 @@ class Table:
|
|||
justify (JustifyValues, optional): Alignment for cells. Defaults to "left".
|
||||
width (int, optional): A minimum width in characters. Defaults to None.
|
||||
ratio (int, optional): Flexible ratio for the column. Defaults to None.
|
||||
no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column.
|
||||
"""
|
||||
|
||||
def _pick_first_style(
|
||||
*values: Optional[Union[Style, str]]
|
||||
) -> Union[Style, str]:
|
||||
"""Pick first non-None style."""
|
||||
for value in values:
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError("expected at least one non-None style")
|
||||
|
||||
column = Column(
|
||||
header=header,
|
||||
footer=footer,
|
||||
header_style=_pick_first_style(
|
||||
header_style=Style.pick_first(
|
||||
header_style, self.header_style, "table.header"
|
||||
),
|
||||
footer_style=_pick_first_style(
|
||||
footer_style=Style.pick_first(
|
||||
footer_style, self.footer_style, "table.footer"
|
||||
),
|
||||
style=_pick_first_style(style, self.style, "table.cell"),
|
||||
style=Style.pick_first(style, self.style, "table.cell"),
|
||||
justify=justify,
|
||||
width=width,
|
||||
ratio=ratio,
|
||||
no_wrap=no_wrap,
|
||||
)
|
||||
self.columns.append(column)
|
||||
|
||||
|
@ -232,16 +230,30 @@ class Table:
|
|||
max_width -= 2
|
||||
widths = self._calculate_column_widths(max_width)
|
||||
table_width = sum(widths) + len(self.columns) + (2 if self.box else 0)
|
||||
if self.title:
|
||||
title_text = Text.from_markup(
|
||||
self.title,
|
||||
style=self.title_style
|
||||
if self.title_style is not None
|
||||
else "table.title",
|
||||
)
|
||||
wrapped_title = title_text.wrap(table_width, "center")
|
||||
yield wrapped_title
|
||||
yield from self.render_title(table_width)
|
||||
yield from self._render(console, options, widths)
|
||||
yield from self.render_caption(table_width)
|
||||
|
||||
def render_title(self, table_width: int) -> "RenderResult":
|
||||
if self.title:
|
||||
if isinstance(self.title, str):
|
||||
title_text = Text.from_markup(
|
||||
self.title, style=Style.pick_first(self.title_style, "table.title")
|
||||
)
|
||||
else:
|
||||
title_text = self.title
|
||||
yield title_text.wrap(table_width, "center")
|
||||
|
||||
def render_caption(self, table_width: int) -> "RenderResult":
|
||||
if self.caption:
|
||||
if isinstance(self.caption, str):
|
||||
caption_text = Text.from_markup(
|
||||
self.caption,
|
||||
style=Style.pick_first(self.caption_style, "table.caption"),
|
||||
)
|
||||
else:
|
||||
caption_text = self.caption
|
||||
yield caption_text.wrap(table_width, "center")
|
||||
|
||||
def _calculate_column_widths(self, max_width: int) -> List[int]:
|
||||
"""Calculate the widths of each column."""
|
||||
|
@ -339,10 +351,14 @@ class Table:
|
|||
_min, _max = get_render_width(cell.renderable, max_width)
|
||||
append_min(_min)
|
||||
append_max(_max)
|
||||
return RenderWidth(
|
||||
max(min_widths) if min_widths else 1,
|
||||
max(max_widths) if max_widths else max_width,
|
||||
)
|
||||
if column.no_wrap:
|
||||
_width = max(max_widths) if max_widths else max_width
|
||||
return RenderWidth(_width, _width)
|
||||
else:
|
||||
return RenderWidth(
|
||||
max(min_widths) if min_widths else 1,
|
||||
max(max_widths) if max_widths else max_width,
|
||||
)
|
||||
|
||||
def _render(
|
||||
self, console: "Console", options: "ConsoleOptions", widths: List[int]
|
||||
|
@ -366,6 +382,7 @@ class Table:
|
|||
|
||||
if box and show_edge:
|
||||
yield Segment(box.get_top(widths), border_style)
|
||||
yield new_line
|
||||
|
||||
for first, last, row in iter_first_last(rows):
|
||||
max_height = 1
|
||||
|
@ -389,6 +406,7 @@ class Table:
|
|||
yield Segment(
|
||||
box.get_row(widths, "foot", edge=show_edge), border_style
|
||||
)
|
||||
yield new_line
|
||||
if first:
|
||||
left = Segment(box.head_left, border_style)
|
||||
right = Segment(box.head_right, border_style)
|
||||
|
@ -419,9 +437,11 @@ class Table:
|
|||
yield new_line
|
||||
if box and first and show_header:
|
||||
yield Segment(box.get_row(widths, "head", edge=show_edge), border_style)
|
||||
yield new_line
|
||||
|
||||
if box and show_edge:
|
||||
yield Segment(box.get_bottom(widths), border_style)
|
||||
yield new_line
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
|
|
@ -2,6 +2,7 @@ from collections.abc import Mapping
|
|||
|
||||
|
||||
from . import box
|
||||
from .highlighter import ReprHighlighter
|
||||
from .pretty import Pretty
|
||||
from .table import Table
|
||||
from .text import Text
|
||||
|
@ -20,6 +21,9 @@ def tabulate_mapping(mapping: Mapping, title: str = None) -> Table:
|
|||
table = Table(
|
||||
show_header=False, title=title, box=box.ROUNDED, border_style="blue", padding=0
|
||||
)
|
||||
highlighter = ReprHighlighter()
|
||||
for key, value in mapping.items():
|
||||
table.add_row(Pretty(key), Pretty(value))
|
||||
table.add_row(
|
||||
Pretty(key, highlighter=highlighter), Pretty(value, highlighter=highlighter)
|
||||
)
|
||||
return table
|
||||
|
|
|
@ -24,7 +24,7 @@ if TYPE_CHECKING: # pragma: no cover
|
|||
from .containers import Lines
|
||||
from .style import Style
|
||||
from .segment import Segment
|
||||
from ._render_width import RenderWidth
|
||||
from .render_width import RenderWidth
|
||||
from ._tools import iter_last, iter_first_last
|
||||
from ._wrap import divide_line
|
||||
|
||||
|
|
2
tests/pytest.ini
Normal file
2
tests/pytest.ini
Normal file
|
@ -0,0 +1,2 @@
|
|||
[pytest]
|
||||
junit_family=legacy
|
|
@ -2,7 +2,7 @@ import pytest
|
|||
|
||||
from rich.console import Console
|
||||
from rich.text import Span, Text
|
||||
from rich._render_width import RenderWidth
|
||||
from rich.render_width import RenderWidth
|
||||
|
||||
|
||||
def test_span():
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue