diff --git a/.gitignore b/.gitignore index 233804adf..bc0dfdb71 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ mypy_report docs/build docs/source/_build tools/*.txt +playground/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index de322cbf7..57530923c 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -7,3 +7,4 @@ The following people have contributed to the development of Rich: - [Oleksis Fraga](https://github.com/oleksis) - [Hedy Li](https://github.com/hedythedev) - [Will McGugan](https://github.com/willmcgugan) +- [Nathan Page](https://github.com/nathanrpage97) diff --git a/docs/source/index.rst b/docs/source/index.rst index d7cf61342..f40541a2f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -19,16 +19,17 @@ Welcome to Rich's documentation! logging.rst traceback.rst prompt.rst - + tables.rst padding.rst - panel.rst - group.rst + panel.rst + group.rst columns.rst + live.rst progress.rst markdown.rst - syntax.rst - + syntax.rst + protocol.rst reference.rst diff --git a/docs/source/live.rst b/docs/source/live.rst new file mode 100644 index 000000000..f1d6bb4aa --- /dev/null +++ b/docs/source/live.rst @@ -0,0 +1,172 @@ +.. _live: + +Live Display +============ + +Rich can display continiuously updated information for any renderable. + +To see some live display examples, try this from the command line:: + + python -m rich.live + +.. note:: + + If you see ellipsis "...", this indicates that the terminal is not tall enough to show the full table. + +Basic Usage +----------- + +The basic usage can be split into two use cases. + +1. Same Renderable +~~~~~~~~~~~~~~~~~~ + +When keeping the same renderable, you simply pass the :class:`~rich.console.RenderableType` you would like to see updating and provide +a ``refresh_per_second`` parameter. The Live :class:`~rich.live.Live` will automatically update the console at the provided refresh rate. + + +**Example**:: + + import time + + from rich.live import Live + from rich.table import Table + + table = Table() + table.add_column("Row ID") + table.add_column("Description") + table.add_column("Level") + + with Live(table, refresh_per_second=4): # update 4 times a second to feel fluid + for row in range(12): + time.sleep(0.4) # arbitrary delay + # update the renderable internally + table.add_row(f"{row}", f"description {row}", "[red]ERROR") + + +2. New Renderable +~~~~~~~~~~~~~~~~~ + +You can also provide constant new renderable to :class:`~rich.live.Live` using the :meth:`~rich.live.Live.update` function. This allows you to +completely change what is rendered live. + +**Example**:: + + import random + import time + + from rich.live import Live + from rich.table import Table + + + def generate_table() -> Table: + + table = Table() + table.add_column("ID") + table.add_column("Value") + table.add_column("Status") + + for row in range(random.randint(2, 6)): + value = random.random() * 100 + table.add_row( + f"{row}", f"{value:3.2f}", "[red]ERROR" if value < 50 else "[green]SUCCESS" + ) + return table + + + with Live(refresh_per_second=4) as live: + for _ in range(40): + time.sleep(0.4) + live.update(generate_table()) + +Advanced Usage +-------------- + +Transient Display +~~~~~~~~~~~~~~~~~ + +Normally when you exit live context manager (or call :meth:`~rich.live.Live.stop`) the last refreshed item remains in the terminal with the cursor on the following line. +You can also make the live display disappear on exit by setting ``transient=True`` on the Live constructor. Here's an example:: + + with Live(transient=True) as live: + ... + +Auto refresh +~~~~~~~~~~~~ + +By default, the live display will refresh 4 times a second. You can set the refresh rate with the ``refresh_per_second`` argument on the :class:`~rich.live.Live` constructor. +You should set this to something lower than 4 if you know your updates will not be that frequent or higher for a smoother feeling. + +You might want to disable auto-refresh entirely if your updates are not very frequent, which you can do by setting ``auto_refresh=False`` on the constructor. +If you disable auto-refresh you will need to call :meth:`~rich.live.Live.refresh` manually or :meth:`~rich.live.Live.update` with ``refresh=True``. + +Vertical Overflow +~~~~~~~~~~~~~~~~~ + +By default, the live display will display ellipsis if the renderable is too large for the terminal. You can adjust this by setting the +``vertical_overflow`` argument on the :class:`~rich.live.Live` constructor. + +- crop: Show renderable up to the terminal height. The rest is hidden. +- ellipsis: Similar to crop except last line of the terminal is replaced with "...". This is the default behavior. +- visible: Will allow the whole renderable to be shown. Note that the display cannot be properly cleared in this mode. + +.. note:: + + Once the live display stops on a non-transient renderable, the last frame will render as **visible** since it doesn't have to be cleared. + +Complex Renders +~~~~~~~~~~~~~~~ + +Refer to the :ref:`Render Groups` about combining multiple :class:`RenderableType` together so that it may be passed into the :class:`~rich.live.Live` constructor +or :meth:`~rich.live.Live.update` method. + +For more powerful structuring it is also possible to use nested tables. + + +Print / log +~~~~~~~~~~~ + +The Live class will create an internal Console object which you can access via ``live.console``. If you print or log to this console, the output will be displayed *above* the live display. Here's an example:: + + import time + + from rich.live import Live + from rich.table import Table + + table = Table() + table.add_column("Row ID") + table.add_column("Description") + table.add_column("Level") + + with Live(table, refresh_per_second=4): # update 4 times a second to feel fluid + for row in range(12): + live.console.print("Working on row #{row}") + time.sleep(0.4) + table.add_row(f"{row}", f"description {row}", "[red]ERROR") + + +If you have another Console object you want to use, pass it in to the :class:`~rich.live.Live` constructor. Here's an example:: + + from my_project import my_console + + with Live(console=my_console) as live: + my_console.print("[bold blue]Starting work!") + ... + +.. note:: + + If you are passing in a file console, the live display only show the last item once the live context is left. + +Redirecting stdout / stderr +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To avoid breaking the live display visuals, Rich will redirect ``stdout`` and ``stderr`` so that you can use the builtin ``print`` statement. +This feature is enabled by default, but you can disable by setting ``redirect_stdout`` or ``redirect_stderr`` to ``False``. + + +Examples +-------- + +See `table_movie.py `_ and +`top_lite_simulator.py `_ +for deeper examples of live displaying. diff --git a/docs/source/reference.rst b/docs/source/reference.rst index fe87ad5c8..a4a324555 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -3,15 +3,16 @@ Reference .. toctree:: :maxdepth: 3 - + reference/align.rst reference/bar.rst reference/color.rst reference/columns.rst reference/console.rst reference/emoji.rst - reference/highlighter.rst + reference/highlighter.rst reference/init.rst + reference/live.rst reference/logging.rst reference/markdown.rst reference/markup.rst diff --git a/docs/source/reference/live.rst b/docs/source/reference/live.rst new file mode 100644 index 000000000..516ed8f24 --- /dev/null +++ b/docs/source/reference/live.rst @@ -0,0 +1,5 @@ +rich.live +========= + +.. automodule:: rich.live + :members: \ No newline at end of file diff --git a/examples/table_movie.py b/examples/table_movie.py index fe9c753bb..627408e46 100644 --- a/examples/table_movie.py +++ b/examples/table_movie.py @@ -1,12 +1,16 @@ +"""Same as the table_movie.py but uses Live to update""" from contextlib import contextmanager import time from rich.console import Console +from rich.columns import Columns from rich.table import Table from rich.measure import Measurement from rich import box from rich.text import Text +from rich.live import Live + TABLE_DATA = [ [ "May 25, 1977", @@ -60,136 +64,110 @@ @contextmanager def beat(length: int = 1) -> None: with console: - console.clear() yield time.sleep(length * BEAT_TIME) table = Table(show_footer=False) +table_centered = Columns((table,), align="center", expand=True) console.clear() -console.show_cursor(False) -try: - table.add_column("Release Date", no_wrap=True) +with Live( + table_centered, console=console, refresh_per_second=10, vertical_overflow="ellipsis" +): + with beat(10): - console.print(table, justify="center") + table.add_column("Release Date", no_wrap=True) - table.add_column("Title", Text.from_markup("[b]Total", justify="right")) with beat(10): - console.print(table, justify="center") + table.add_column("Title", Text.from_markup("[b]Total", justify="right")) - table.add_column("Budget", "[u]$412,000,000", no_wrap=True) with beat(10): - console.print(table, justify="center") + table.add_column("Budget", "[u]$412,000,000", no_wrap=True) - table.add_column("Opening Weekend", "[u]$577,703,455", no_wrap=True) with beat(10): - console.print(table, justify="center") + table.add_column("Opening Weekend", "[u]$577,703,455", no_wrap=True) - table.add_column("Box Office", "[u]$4,331,212,357", no_wrap=True) with beat(10): - console.print(table, justify="center") + table.add_column("Box Office", "[u]$4,331,212,357", no_wrap=True) - table.title = "Star Wars Box Office" with beat(10): - console.print(table, justify="center") + table.title = "Star Wars Box Office" - table.title = ( - "[not italic]:popcorn:[/] Star Wars Box Office [not italic]:popcorn:[/]" - ) with beat(10): - console.print(table, justify="center") + table.title = ( + "[not italic]:popcorn:[/] Star Wars Box Office [not italic]:popcorn:[/]" + ) - table.caption = "Made with Rich" with beat(10): - console.print(table, justify="center") + table.caption = "Made with Rich" - table.caption = "Made with [b]Rich[/b]" with beat(10): - console.print(table, justify="center") + table.caption = "Made with [b]Rich[/b]" - table.caption = "Made with [b magenta not dim]Rich[/]" with beat(10): - console.print(table, justify="center") + table.caption = "Made with [b magenta not dim]Rich[/]" for row in TABLE_DATA: - table.add_row(*row) with beat(10): - console.print(table, justify="center") + table.add_row(*row) - table.show_footer = True with beat(10): - console.print(table, justify="center") + table.show_footer = True table_width = Measurement.get(console, table, console.width).maximum - table.columns[2].justify = "right" with beat(10): - console.print(table, justify="center") + table.columns[2].justify = "right" - table.columns[3].justify = "right" with beat(10): - console.print(table, justify="center") + table.columns[3].justify = "right" - table.columns[4].justify = "right" with beat(10): - console.print(table, justify="center") + table.columns[4].justify = "right" - table.columns[2].header_style = "bold red" with beat(10): - console.print(table, justify="center") + table.columns[2].header_style = "bold red" - table.columns[3].header_style = "bold green" with beat(10): - console.print(table, justify="center") + table.columns[3].header_style = "bold green" - table.columns[4].header_style = "bold blue" with beat(10): - console.print(table, justify="center") + table.columns[4].header_style = "bold blue" - table.columns[2].style = "red" with beat(10): - console.print(table, justify="center") + table.columns[2].style = "red" - table.columns[3].style = "green" with beat(10): - console.print(table, justify="center") + table.columns[3].style = "green" - table.columns[4].style = "blue" with beat(10): - console.print(table, justify="center") + table.columns[4].style = "blue" - table.columns[0].style = "cyan" - table.columns[0].header_style = "bold cyan" with beat(10): - console.print(table, justify="center") + table.columns[0].style = "cyan" + table.columns[0].header_style = "bold cyan" - table.columns[1].style = "magenta" - table.columns[1].header_style = "bold magenta" with beat(10): - console.print(table, justify="center") + table.columns[1].style = "magenta" + table.columns[1].header_style = "bold magenta" - table.columns[2].footer_style = "bright_red" with beat(10): - console.print(table, justify="center") + table.columns[2].footer_style = "bright_red" - table.columns[3].footer_style = "bright_green" with beat(10): - console.print(table, justify="center") + table.columns[3].footer_style = "bright_green" - table.columns[4].footer_style = "bright_blue" with beat(10): - console.print(table, justify="center") + table.columns[4].footer_style = "bright_blue" - table.row_styles = ["none", "dim"] with beat(10): - console.print(table, justify="center") + table.row_styles = ["none", "dim"] - table.border_style = "bright_yellow" with beat(10): - console.print(table, justify="center") + table.border_style = "bright_yellow" for box in [ box.SQUARE, @@ -197,39 +175,29 @@ def beat(length: int = 1) -> None: box.SIMPLE, box.SIMPLE_HEAD, ]: - table.box = box with beat(10): - console.print(table, justify="center") + table.box = box - table.pad_edge = False with beat(10): - console.print(table, justify="center") + table.pad_edge = False original_width = Measurement.get(console, table).maximum for width in range(original_width, console.width, 2): - table.width = width with beat(2): - console.print(table, justify="center") + table.width = width for width in range(console.width, original_width, -2): - table.width = width with beat(2): - console.print(table, justify="center") + table.width = width for width in range(original_width, 90, -2): - table.width = width with beat(2): - console.print(table, justify="center") + table.width = width for width in range(90, original_width + 1, 2): - table.width = width with beat(2): - console.print(table, justify="center") + table.width = width - table.width = None with beat(2): - console.print(table, justify="center") - -finally: - console.show_cursor(True) + table.width = None diff --git a/examples/top_lite_simulator.py b/examples/top_lite_simulator.py new file mode 100644 index 000000000..df605ee47 --- /dev/null +++ b/examples/top_lite_simulator.py @@ -0,0 +1,81 @@ +"""Lite simulation of the top linux command.""" + +import datetime +import random +import time +from dataclasses import dataclass + +from rich import box +from rich.console import Console +from rich.live import Live +from rich.table import Table +from typing_extensions import Literal + + +@dataclass +class Process: + pid: int + command: str + cpu_percent: float + memory: int + start_time: datetime.datetime + thread_count: int + state: Literal["running", "sleeping"] + + @property + def memory_str(self) -> str: + if self.memory > 1e6: + return f"{int(self.memory/1e6)}M" + if self.memory > 1e3: + return f"{int(self.memory/1e3)}K" + return str(self.memory) + + @property + def time_str(self) -> str: + return str(datetime.datetime.now() - self.start_time) + + +def generate_process(pid: int) -> Process: + return Process( + pid=pid, + command=f"Process {pid}", + cpu_percent=random.random() * 20, + memory=random.randint(10, 200) ** 3, + start_time=datetime.datetime.now() + - datetime.timedelta(seconds=random.randint(0, 500) ** 2), + thread_count=random.randint(1, 32), + state="running" if random.randint(0, 10) < 8 else "sleeping", + ) + + +def create_process_table(height: int) -> Table: + + processes = sorted( + [generate_process(pid) for pid in range(height)], + key=lambda p: p.cpu_percent, + reverse=True, + ) + table = Table( + "PID", "Command", "CPU %", "Memory", "Time", "Thread #", "State", box=box.SIMPLE + ) + + for process in processes: + table.add_row( + str(process.pid), + process.command, + f"{process.cpu_percent:.1f}", + process.memory_str, + process.time_str, + str(process.thread_count), + process.state, + ) + + return table + + +console = Console() + +with Live(console=console, transient=True, refresh_per_second=10) as live: + while True: + live.update(create_process_table(console.size.height - 4)) + time.sleep(1.5) diff --git a/rich/live.py b/rich/live.py new file mode 100644 index 000000000..17c69b81f --- /dev/null +++ b/rich/live.py @@ -0,0 +1,376 @@ +import sys +from threading import Event, RLock, Thread +from typing import IO, Any, List, Optional + +from typing_extensions import Literal + +from .__init__ import get_console +from ._loop import loop_last +from .console import ( + Console, + ConsoleOptions, + ConsoleRenderable, + RenderableType, + RenderHook, + RenderResult, +) +from .control import Control +from .jupyter import JupyterMixin +from .live_render import LiveRender +from .progress import _FileProxy +from .segment import Segment +from .style import Style +from .text import Text + +VerticalOverflowMethod = Literal["crop", "ellipsis", "visible"] + + +class _RefreshThread(Thread): + """A thread that calls refresh() at regular intervals.""" + + def __init__(self, live: "Live", refresh_per_second: float) -> None: + self.live = live + self.refresh_per_second = refresh_per_second + self.done = Event() + super().__init__() + + def stop(self) -> None: + self.done.set() + + def run(self) -> None: + while not self.done.wait(1 / self.refresh_per_second): + with self.live._lock: + self.live.refresh() + + +class _LiveRender(LiveRender): + def __init__(self, live: "Live", renderable: RenderableType) -> None: + self._live = live + self.renderable = renderable + self._shape: Optional[Tuple[int, int]] = None + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + with self._live._lock: + lines = console.render_lines(self.renderable, options, pad=False) + + shape = Segment.get_shape(lines) + _, height = shape + if height > console.size.height: + if self._live.vertical_overflow == "crop": + lines = lines[: console.size.height] + shape = Segment.get_shape(lines) + elif self._live.vertical_overflow == "ellipsis": + lines = lines[: (console.size.height - 1)] + lines.append( + list( + console.render( + Text( + "...", + overflow="crop", + justify="center", + end="", + style=Style(bold=True), + ) + ) + ) + ) + shape = Segment.get_shape(lines) + + self._shape = shape + + for last, line in loop_last(lines): + yield from line + if not last: + yield Segment.line() + + +class Live(JupyterMixin, RenderHook): + """Renders an auto-updating live display of any given renderable. + + Args: + renderable (RenderableType, optional): [The renderable to live display. Defaults to displaying nothing. + console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout. + auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()` or `update()` with refresh flag. Defaults to True + refresh_per_second (float, optional): Number of times per second to refresh the live display. Defaults to 1. + transient (bool, optional): Clear the renderable on exit. Defaults to False. + redirect_stdout (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True. + redirect_stderr (bool, optional): Enable redirection of stderr. Defaults to True. + vertical_overflow (VerticalOverflowMethod, optional): How to handle renderable when it is too tall for the console. Defaults to "ellipsis". + """ + + def __init__( + self, + renderable: RenderableType = "", + *, + console: Console = None, + auto_refresh: bool = True, + refresh_per_second: float = 4, + transient: bool = False, + redirect_stdout: bool = True, + redirect_stderr: bool = True, + vertical_overflow: VerticalOverflowMethod = "ellipsis", + ) -> None: + assert refresh_per_second > 0, "refresh_per_second must be > 0" + self.console = console if console is not None else get_console() + self._live_render = _LiveRender(self, renderable) + + self._redirect_stdout = redirect_stdout + self._redirect_stderr = redirect_stderr + self._restore_stdout: Optional[IO[str]] = None + self._restore_stderr: Optional[IO[str]] = None + + self._lock = RLock() + self.ipy_widget: Optional[Any] = None + self.auto_refresh = auto_refresh + self._started: bool = False + self.transient = transient + + self._refresh_thread: Optional[_RefreshThread] = None + self.refresh_per_second = refresh_per_second + + self.vertical_overflow = vertical_overflow + # cant store just clear_control as the live_render shape is lazily computed on render + + def start(self) -> None: + """Start live rendering display.""" + with self._lock: + if self._started: + return + + self.console.show_cursor(False) + self._enable_redirect_io() + self.console.push_render_hook(self) + self._started = True + + if self.auto_refresh: + self._refresh_thread = _RefreshThread(self, self.refresh_per_second) + self._refresh_thread.start() + + def stop(self) -> None: + """Stop live rendering display.""" + with self._lock: + if not self._started: + return + self._started = False + try: + if self.auto_refresh and self._refresh_thread is not None: + self._refresh_thread.stop() + # allow it to fully render on the last even if overflow + self.vertical_overflow = "visible" + self.refresh() + if self.console.is_terminal: + self.console.line() + finally: + self._disable_redirect_io() + self.console.pop_render_hook() + self.console.show_cursor(True) + + if self.transient: + self.console.control(self._live_render.restore_cursor()) + if self.ipy_widget is not None and self.transient: # pragma: no cover + self.ipy_widget.clear_output() + self.ipy_widget.close() + + def __enter__(self) -> "Live": + self.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + self.stop() + + def _enable_redirect_io(self): + """Enable redirecting of stdout / stderr.""" + if self.console.is_terminal: + if self._redirect_stdout: + self._restore_stdout = sys.stdout + sys.stdout = _FileProxy(self.console, sys.stdout) + if self._redirect_stderr: + self._restore_stderr = sys.stderr + sys.stdout = _FileProxy(self.console, sys.stdout) + + @property + def renderable(self) -> RenderableType: + """Get the renderable that is being displayed + + Returns: + RenderableType: Displayed renderable. + """ + with self._lock: + return self._live_render.renderable + + def update(self, renderable: RenderableType, *, refresh: bool = False) -> None: + """Update the renderable that is being displayed + + Args: + renderable (RenderableType): New renderable to use. + refresh (bool, optional): Refresh the display. Defaults to False. + """ + with self._lock: + self._live_render.set_renderable(renderable) + if refresh: + self.refresh() + + def refresh(self) -> None: + """Update the display of the Live Render.""" + if self.console.is_jupyter: # pragma: no cover + try: + from IPython.display import display + from ipywidgets import Output + except ImportError: + import warnings + + warnings.warn('install "ipywidgets" for Jupyter support') + else: + with self._lock: + if self.ipy_widget is None: + self.ipy_widget = Output() + display(self.ipy_widget) + + with self.ipy_widget: + self.ipy_widget.clear_output(wait=True) + self.console.print(self._live_render.renderable) + elif self.console.is_terminal and not self.console.is_dumb_terminal: + with self._lock, self.console: + self.console.print(Control("")) + elif ( + not self._started and not self.transient + ): # if it is finished allow files or dumb-terminals to see final result + with self.console: + self.console.print(Control("")) + + def _disable_redirect_io(self): + """Disable redirecting of stdout / stderr.""" + if self._restore_stdout: + sys.stdout = self._restore_stdout + self._restore_stdout = None + if self._restore_stderr: + sys.stderr = self._restore_stderr + self._restore_stderr = None + + def process_renderables( + self, renderables: List[ConsoleRenderable] + ) -> List[ConsoleRenderable]: + """Process renderables to restore cursor and display progress.""" + if self.console.is_terminal: + # lock needs acquiring as user can modify live_render renerable at any time unlike in Progress. + with self._lock: + # determine the control command needed to clear previous rendering + renderables = [ + self._live_render.position_cursor(), + *renderables, + self._live_render, + ] + elif ( + not self._started and not self.transient + ): # if it is finished render the final output for files or dumb_terminals + renderables = [*renderables, self._live_render] + + return renderables + + +if __name__ == "__main__": # pragma: no cover + import random + import time + from itertools import cycle + from typing import Dict, List, Tuple + + from .console import Console + from .live import Live + from .panel import Panel + from .rule import Rule + from .syntax import Syntax + from .table import Table + + console = Console() + + syntax = Syntax( + '''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + for value in iter_values: + yield False, previous_value + previous_value = value + yield True, previous_value''', + "python", + line_numbers=True, + ) + + table = Table("foo", "bar", "baz") + table.add_row("1", "2", "3") + + progress_renderables = [ + "You can make the terminal shorter and taller to see the live table hide" + "Text may be printed while the progress bars are rendering.", + Panel("In fact, [i]any[/i] renderable will work"), + "Such as [magenta]tables[/]...", + table, + "Pretty printed structures...", + {"type": "example", "text": "Pretty printed"}, + "Syntax...", + syntax, + Rule("Give it a try!"), + ] + + examples = cycle(progress_renderables) + + exchanges = [ + "SGD", + "MYR", + "EUR", + "USD", + "AUD", + "JPY", + "CNH", + "HKD", + "CAD", + "INR", + "DKK", + "GBP", + "RUB", + "NZD", + "MXN", + "IDR", + "TWD", + "THB", + "VND", + ] + with Live(console=console) as live_table: + exchange_rate_dict: Dict[Tuple[str, str], float] = {} + + for index in range(100): + select_exchange = exchanges[index % len(exchanges)] + + for exchange in exchanges: + if exchange == select_exchange: + continue + time.sleep(0.4) + if random.randint(0, 10) < 1: + console.log(next(examples)) + exchange_rate_dict[(select_exchange, exchange)] = 200 / ( + (random.random() * 320) + 1 + ) + if len(exchange_rate_dict) > len(exchanges) - 1: + exchange_rate_dict.pop(list(exchange_rate_dict.keys())[0]) + table = Table(title="Exchange Rates") + + table.add_column("Source Currency") + table.add_column("Destination Currency") + table.add_column("Exchange Rate") + + for ((soure, dest), exchange_rate) in exchange_rate_dict.items(): + table.add_row( + soure, + dest, + Text( + f"{exchange_rate:.4f}", + style="red" if exchange_rate < 1.0 else "green", + ), + ) + + live_table.update(table) diff --git a/tests/_card_render.py b/tests/_card_render.py index 17783d030..8f7f6279d 100644 --- a/tests/_card_render.py +++ b/tests/_card_render.py @@ -1 +1 @@ -expected='\x1b[3m Rich features \x1b[0m\n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Colors \x1b[0m\x1b[1;31m \x1b[0m✓ \x1b[1;32m4-bit color\x1b[0m \x1b[38;2;86;0;0;48;2;51;0;0m▄\x1b[0m\x1b[38;2;86;9;0;48;2;51;5;0m▄\x1b[0m\x1b[38;2;86;18;0;48;2;51;11;0m▄\x1b[0m\x1b[38;2;86;28;0;48;2;51;16;0m▄\x1b[0m\x1b[38;2;86;37;0;48;2;51;22;0m▄\x1b[0m\x1b[38;2;86;47;0;48;2;51;27;0m▄\x1b[0m\x1b[38;2;86;56;0;48;2;51;33;0m▄\x1b[0m\x1b[38;2;86;66;0;48;2;51;38;0m▄\x1b[0m\x1b[38;2;86;75;0;48;2;51;44;0m▄\x1b[0m\x1b[38;2;86;85;0;48;2;51;50;0m▄\x1b[0m\x1b[38;2;78;86;0;48;2;46;51;0m▄\x1b[0m\x1b[38;2;69;86;0;48;2;40;51;0m▄\x1b[0m\x1b[38;2;59;86;0;48;2;35;51;0m▄\x1b[0m\x1b[38;2;50;86;0;48;2;29;51;0m▄\x1b[0m\x1b[38;2;40;86;0;48;2;24;51;0m▄\x1b[0m\x1b[38;2;31;86;0;48;2;18;51;0m▄\x1b[0m\x1b[38;2;22;86;0;48;2;12;51;0m▄\x1b[0m\x1b[38;2;12;86;0;48;2;7;51;0m▄\x1b[0m\x1b[38;2;3;86;0;48;2;1;51;0m▄\x1b[0m\x1b[38;2;0;86;6;48;2;0;51;3m▄\x1b[0m\x1b[38;2;0;86;15;48;2;0;51;9m▄\x1b[0m\x1b[38;2;0;86;25;48;2;0;51;14m▄\x1b[0m\x1b[38;2;0;86;34;48;2;0;51;20m▄\x1b[0m\x1b[38;2;0;86;44;48;2;0;51;25m▄\x1b[0m\x1b[38;2;0;86;53;48;2;0;51;31m▄\x1b[0m\x1b[38;2;0;86;63;48;2;0;51;37m▄\x1b[0m\x1b[38;2;0;86;72;48;2;0;51;42m▄\x1b[0m\x1b[38;2;0;86;81;48;2;0;51;48m▄\x1b[0m\x1b[38;2;0;81;86;48;2;0;48;51m▄\x1b[0m\x1b[38;2;0;72;86;48;2;0;42;51m▄\x1b[0m\x1b[38;2;0;63;86;48;2;0;37;51m▄\x1b[0m\x1b[38;2;0;53;86;48;2;0;31;51m▄\x1b[0m\x1b[38;2;0;44;86;48;2;0;25;51m▄\x1b[0m\x1b[38;2;0;34;86;48;2;0;20;51m▄\x1b[0m\x1b[38;2;0;25;86;48;2;0;14;51m▄\x1b[0m\x1b[38;2;0;15;86;48;2;0;9;51m▄\x1b[0m\x1b[38;2;0;6;86;48;2;0;3;51m▄\x1b[0m\x1b[38;2;3;0;86;48;2;1;0;51m▄\x1b[0m\x1b[38;2;12;0;86;48;2;7;0;51m▄\x1b[0m\x1b[38;2;22;0;86;48;2;12;0;51m▄\x1b[0m\x1b[38;2;31;0;86;48;2;18;0;51m▄\x1b[0m\x1b[38;2;40;0;86;48;2;24;0;51m▄\x1b[0m\x1b[38;2;50;0;86;48;2;29;0;51m▄\x1b[0m\x1b[38;2;59;0;86;48;2;35;0;51m▄\x1b[0m\x1b[38;2;69;0;86;48;2;40;0;51m▄\x1b[0m\x1b[38;2;78;0;86;48;2;46;0;51m▄\x1b[0m\x1b[38;2;86;0;85;48;2;51;0;50m▄\x1b[0m\x1b[38;2;86;0;75;48;2;51;0;44m▄\x1b[0m\x1b[38;2;86;0;66;48;2;51;0;38m▄\x1b[0m\x1b[38;2;86;0;56;48;2;51;0;33m▄\x1b[0m\x1b[38;2;86;0;47;48;2;51;0;27m▄\x1b[0m\x1b[38;2;86;0;37;48;2;51;0;22m▄\x1b[0m\x1b[38;2;86;0;28;48;2;51;0;16m▄\x1b[0m\x1b[38;2;86;0;18;48;2;51;0;11m▄\x1b[0m\x1b[38;2;86;0;9;48;2;51;0;5m▄\x1b[0m \n ✓ \x1b[1;34m8-bit color\x1b[0m \x1b[38;2;158;0;0;48;2;122;0;0m▄\x1b[0m\x1b[38;2;158;17;0;48;2;122;13;0m▄\x1b[0m\x1b[38;2;158;34;0;48;2;122;26;0m▄\x1b[0m\x1b[38;2;158;51;0;48;2;122;40;0m▄\x1b[0m\x1b[38;2;158;68;0;48;2;122;53;0m▄\x1b[0m\x1b[38;2;158;86;0;48;2;122;66;0m▄\x1b[0m\x1b[38;2;158;103;0;48;2;122;80;0m▄\x1b[0m\x1b[38;2;158;120;0;48;2;122;93;0m▄\x1b[0m\x1b[38;2;158;137;0;48;2;122;106;0m▄\x1b[0m\x1b[38;2;158;155;0;48;2;122;120;0m▄\x1b[0m\x1b[38;2;143;158;0;48;2;111;122;0m▄\x1b[0m\x1b[38;2;126;158;0;48;2;97;122;0m▄\x1b[0m\x1b[38;2;109;158;0;48;2;84;122;0m▄\x1b[0m\x1b[38;2;91;158;0;48;2;71;122;0m▄\x1b[0m\x1b[38;2;74;158;0;48;2;57;122;0m▄\x1b[0m\x1b[38;2;57;158;0;48;2;44;122;0m▄\x1b[0m\x1b[38;2;40;158;0;48;2;31;122;0m▄\x1b[0m\x1b[38;2;22;158;0;48;2;17;122;0m▄\x1b[0m\x1b[38;2;5;158;0;48;2;4;122;0m▄\x1b[0m\x1b[38;2;0;158;11;48;2;0;122;8m▄\x1b[0m\x1b[38;2;0;158;28;48;2;0;122;22m▄\x1b[0m\x1b[38;2;0;158;45;48;2;0;122;35m▄\x1b[0m\x1b[38;2;0;158;63;48;2;0;122;48m▄\x1b[0m\x1b[38;2;0;158;80;48;2;0;122;62m▄\x1b[0m\x1b[38;2;0;158;97;48;2;0;122;75m▄\x1b[0m\x1b[38;2;0;158;114;48;2;0;122;89m▄\x1b[0m\x1b[38;2;0;158;132;48;2;0;122;102m▄\x1b[0m\x1b[38;2;0;158;149;48;2;0;122;115m▄\x1b[0m\x1b[38;2;0;149;158;48;2;0;115;122m▄\x1b[0m\x1b[38;2;0;132;158;48;2;0;102;122m▄\x1b[0m\x1b[38;2;0;114;158;48;2;0;89;122m▄\x1b[0m\x1b[38;2;0;97;158;48;2;0;75;122m▄\x1b[0m\x1b[38;2;0;80;158;48;2;0;62;122m▄\x1b[0m\x1b[38;2;0;63;158;48;2;0;48;122m▄\x1b[0m\x1b[38;2;0;45;158;48;2;0;35;122m▄\x1b[0m\x1b[38;2;0;28;158;48;2;0;22;122m▄\x1b[0m\x1b[38;2;0;11;158;48;2;0;8;122m▄\x1b[0m\x1b[38;2;5;0;158;48;2;4;0;122m▄\x1b[0m\x1b[38;2;22;0;158;48;2;17;0;122m▄\x1b[0m\x1b[38;2;40;0;158;48;2;31;0;122m▄\x1b[0m\x1b[38;2;57;0;158;48;2;44;0;122m▄\x1b[0m\x1b[38;2;74;0;158;48;2;57;0;122m▄\x1b[0m\x1b[38;2;91;0;158;48;2;71;0;122m▄\x1b[0m\x1b[38;2;109;0;158;48;2;84;0;122m▄\x1b[0m\x1b[38;2;126;0;158;48;2;97;0;122m▄\x1b[0m\x1b[38;2;143;0;158;48;2;111;0;122m▄\x1b[0m\x1b[38;2;158;0;155;48;2;122;0;120m▄\x1b[0m\x1b[38;2;158;0;137;48;2;122;0;106m▄\x1b[0m\x1b[38;2;158;0;120;48;2;122;0;93m▄\x1b[0m\x1b[38;2;158;0;103;48;2;122;0;80m▄\x1b[0m\x1b[38;2;158;0;86;48;2;122;0;66m▄\x1b[0m\x1b[38;2;158;0;68;48;2;122;0;53m▄\x1b[0m\x1b[38;2;158;0;51;48;2;122;0;40m▄\x1b[0m\x1b[38;2;158;0;34;48;2;122;0;26m▄\x1b[0m\x1b[38;2;158;0;17;48;2;122;0;13m▄\x1b[0m \n ✓ \x1b[1;35mTruecolor (16.7 million)\x1b[0m \x1b[38;2;229;0;0;48;2;193;0;0m▄\x1b[0m\x1b[38;2;229;25;0;48;2;193;21;0m▄\x1b[0m\x1b[38;2;229;50;0;48;2;193;42;0m▄\x1b[0m\x1b[38;2;229;75;0;48;2;193;63;0m▄\x1b[0m\x1b[38;2;229;100;0;48;2;193;84;0m▄\x1b[0m\x1b[38;2;229;125;0;48;2;193;105;0m▄\x1b[0m\x1b[38;2;229;150;0;48;2;193;126;0m▄\x1b[0m\x1b[38;2;229;175;0;48;2;193;147;0m▄\x1b[0m\x1b[38;2;229;200;0;48;2;193;169;0m▄\x1b[0m\x1b[38;2;229;225;0;48;2;193;190;0m▄\x1b[0m\x1b[38;2;208;229;0;48;2;176;193;0m▄\x1b[0m\x1b[38;2;183;229;0;48;2;155;193;0m▄\x1b[0m\x1b[38;2;158;229;0;48;2;133;193;0m▄\x1b[0m\x1b[38;2;133;229;0;48;2;112;193;0m▄\x1b[0m\x1b[38;2;108;229;0;48;2;91;193;0m▄\x1b[0m\x1b[38;2;83;229;0;48;2;70;193;0m▄\x1b[0m\x1b[38;2;58;229;0;48;2;49;193;0m▄\x1b[0m\x1b[38;2;33;229;0;48;2;28;193;0m▄\x1b[0m\x1b[38;2;8;229;0;48;2;7;193;0m▄\x1b[0m\x1b[38;2;0;229;16;48;2;0;193;14m▄\x1b[0m\x1b[38;2;0;229;41;48;2;0;193;35m▄\x1b[0m\x1b[38;2;0;229;66;48;2;0;193;56m▄\x1b[0m\x1b[38;2;0;229;91;48;2;0;193;77m▄\x1b[0m\x1b[38;2;0;229;116;48;2;0;193;98m▄\x1b[0m\x1b[38;2;0;229;141;48;2;0;193;119m▄\x1b[0m\x1b[38;2;0;229;166;48;2;0;193;140m▄\x1b[0m\x1b[38;2;0;229;191;48;2;0;193;162m▄\x1b[0m\x1b[38;2;0;229;216;48;2;0;193;183m▄\x1b[0m\x1b[38;2;0;216;229;48;2;0;183;193m▄\x1b[0m\x1b[38;2;0;191;229;48;2;0;162;193m▄\x1b[0m\x1b[38;2;0;166;229;48;2;0;140;193m▄\x1b[0m\x1b[38;2;0;141;229;48;2;0;119;193m▄\x1b[0m\x1b[38;2;0;116;229;48;2;0;98;193m▄\x1b[0m\x1b[38;2;0;91;229;48;2;0;77;193m▄\x1b[0m\x1b[38;2;0;66;229;48;2;0;56;193m▄\x1b[0m\x1b[38;2;0;41;229;48;2;0;35;193m▄\x1b[0m\x1b[38;2;0;16;229;48;2;0;14;193m▄\x1b[0m\x1b[38;2;8;0;229;48;2;7;0;193m▄\x1b[0m\x1b[38;2;33;0;229;48;2;28;0;193m▄\x1b[0m\x1b[38;2;58;0;229;48;2;49;0;193m▄\x1b[0m\x1b[38;2;83;0;229;48;2;70;0;193m▄\x1b[0m\x1b[38;2;108;0;229;48;2;91;0;193m▄\x1b[0m\x1b[38;2;133;0;229;48;2;112;0;193m▄\x1b[0m\x1b[38;2;158;0;229;48;2;133;0;193m▄\x1b[0m\x1b[38;2;183;0;229;48;2;155;0;193m▄\x1b[0m\x1b[38;2;208;0;229;48;2;176;0;193m▄\x1b[0m\x1b[38;2;229;0;225;48;2;193;0;190m▄\x1b[0m\x1b[38;2;229;0;200;48;2;193;0;169m▄\x1b[0m\x1b[38;2;229;0;175;48;2;193;0;147m▄\x1b[0m\x1b[38;2;229;0;150;48;2;193;0;126m▄\x1b[0m\x1b[38;2;229;0;125;48;2;193;0;105m▄\x1b[0m\x1b[38;2;229;0;100;48;2;193;0;84m▄\x1b[0m\x1b[38;2;229;0;75;48;2;193;0;63m▄\x1b[0m\x1b[38;2;229;0;50;48;2;193;0;42m▄\x1b[0m\x1b[38;2;229;0;25;48;2;193;0;21m▄\x1b[0m \n ✓ \x1b[1;33mDumb terminals\x1b[0m \x1b[38;2;254;45;45;48;2;255;10;10m▄\x1b[0m\x1b[38;2;254;68;45;48;2;255;36;10m▄\x1b[0m\x1b[38;2;254;91;45;48;2;255;63;10m▄\x1b[0m\x1b[38;2;254;114;45;48;2;255;90;10m▄\x1b[0m\x1b[38;2;254;137;45;48;2;255;117;10m▄\x1b[0m\x1b[38;2;254;159;45;48;2;255;143;10m▄\x1b[0m\x1b[38;2;254;182;45;48;2;255;170;10m▄\x1b[0m\x1b[38;2;254;205;45;48;2;255;197;10m▄\x1b[0m\x1b[38;2;254;228;45;48;2;255;223;10m▄\x1b[0m\x1b[38;2;254;251;45;48;2;255;250;10m▄\x1b[0m\x1b[38;2;235;254;45;48;2;232;255;10m▄\x1b[0m\x1b[38;2;213;254;45;48;2;206;255;10m▄\x1b[0m\x1b[38;2;190;254;45;48;2;179;255;10m▄\x1b[0m\x1b[38;2;167;254;45;48;2;152;255;10m▄\x1b[0m\x1b[38;2;144;254;45;48;2;125;255;10m▄\x1b[0m\x1b[38;2;121;254;45;48;2;99;255;10m▄\x1b[0m\x1b[38;2;99;254;45;48;2;72;255;10m▄\x1b[0m\x1b[38;2;76;254;45;48;2;45;255;10m▄\x1b[0m\x1b[38;2;53;254;45;48;2;19;255;10m▄\x1b[0m\x1b[38;2;45;254;61;48;2;10;255;28m▄\x1b[0m\x1b[38;2;45;254;83;48;2;10;255;54m▄\x1b[0m\x1b[38;2;45;254;106;48;2;10;255;81m▄\x1b[0m\x1b[38;2;45;254;129;48;2;10;255;108m▄\x1b[0m\x1b[38;2;45;254;152;48;2;10;255;134m▄\x1b[0m\x1b[38;2;45;254;175;48;2;10;255;161m▄\x1b[0m\x1b[38;2;45;254;197;48;2;10;255;188m▄\x1b[0m\x1b[38;2;45;254;220;48;2;10;255;214m▄\x1b[0m\x1b[38;2;45;254;243;48;2;10;255;241m▄\x1b[0m\x1b[38;2;45;243;254;48;2;10;241;255m▄\x1b[0m\x1b[38;2;45;220;254;48;2;10;214;255m▄\x1b[0m\x1b[38;2;45;197;254;48;2;10;188;255m▄\x1b[0m\x1b[38;2;45;175;254;48;2;10;161;255m▄\x1b[0m\x1b[38;2;45;152;254;48;2;10;134;255m▄\x1b[0m\x1b[38;2;45;129;254;48;2;10;108;255m▄\x1b[0m\x1b[38;2;45;106;254;48;2;10;81;255m▄\x1b[0m\x1b[38;2;45;83;254;48;2;10;54;255m▄\x1b[0m\x1b[38;2;45;61;254;48;2;10;28;255m▄\x1b[0m\x1b[38;2;53;45;254;48;2;19;10;255m▄\x1b[0m\x1b[38;2;76;45;254;48;2;45;10;255m▄\x1b[0m\x1b[38;2;99;45;254;48;2;72;10;255m▄\x1b[0m\x1b[38;2;121;45;254;48;2;99;10;255m▄\x1b[0m\x1b[38;2;144;45;254;48;2;125;10;255m▄\x1b[0m\x1b[38;2;167;45;254;48;2;152;10;255m▄\x1b[0m\x1b[38;2;190;45;254;48;2;179;10;255m▄\x1b[0m\x1b[38;2;213;45;254;48;2;206;10;255m▄\x1b[0m\x1b[38;2;235;45;254;48;2;232;10;255m▄\x1b[0m\x1b[38;2;254;45;251;48;2;255;10;250m▄\x1b[0m\x1b[38;2;254;45;228;48;2;255;10;223m▄\x1b[0m\x1b[38;2;254;45;205;48;2;255;10;197m▄\x1b[0m\x1b[38;2;254;45;182;48;2;255;10;170m▄\x1b[0m\x1b[38;2;254;45;159;48;2;255;10;143m▄\x1b[0m\x1b[38;2;254;45;137;48;2;255;10;117m▄\x1b[0m\x1b[38;2;254;45;114;48;2;255;10;90m▄\x1b[0m\x1b[38;2;254;45;91;48;2;255;10;63m▄\x1b[0m\x1b[38;2;254;45;68;48;2;255;10;36m▄\x1b[0m \n ✓ \x1b[1;36mAutomatic color conversion\x1b[0m \x1b[38;2;255;117;117;48;2;255;81;81m▄\x1b[0m\x1b[38;2;255;132;117;48;2;255;100;81m▄\x1b[0m\x1b[38;2;255;147;117;48;2;255;119;81m▄\x1b[0m\x1b[38;2;255;162;117;48;2;255;138;81m▄\x1b[0m\x1b[38;2;255;177;117;48;2;255;157;81m▄\x1b[0m\x1b[38;2;255;192;117;48;2;255;176;81m▄\x1b[0m\x1b[38;2;255;207;117;48;2;255;195;81m▄\x1b[0m\x1b[38;2;255;222;117;48;2;255;214;81m▄\x1b[0m\x1b[38;2;255;237;117;48;2;255;232;81m▄\x1b[0m\x1b[38;2;255;252;117;48;2;255;251;81m▄\x1b[0m\x1b[38;2;242;255;117;48;2;239;255;81m▄\x1b[0m\x1b[38;2;227;255;117;48;2;220;255;81m▄\x1b[0m\x1b[38;2;212;255;117;48;2;201;255;81m▄\x1b[0m\x1b[38;2;197;255;117;48;2;182;255;81m▄\x1b[0m\x1b[38;2;182;255;117;48;2;163;255;81m▄\x1b[0m\x1b[38;2;167;255;117;48;2;144;255;81m▄\x1b[0m\x1b[38;2;152;255;117;48;2;125;255;81m▄\x1b[0m\x1b[38;2;137;255;117;48;2;106;255;81m▄\x1b[0m\x1b[38;2;122;255;117;48;2;87;255;81m▄\x1b[0m\x1b[38;2;117;255;127;48;2;81;255;94m▄\x1b[0m\x1b[38;2;117;255;142;48;2;81;255;113m▄\x1b[0m\x1b[38;2;117;255;157;48;2;81;255;132m▄\x1b[0m\x1b[38;2;117;255;172;48;2;81;255;150m▄\x1b[0m\x1b[38;2;117;255;187;48;2;81;255;169m▄\x1b[0m\x1b[38;2;117;255;202;48;2;81;255;188m▄\x1b[0m\x1b[38;2;117;255;217;48;2;81;255;207m▄\x1b[0m\x1b[38;2;117;255;232;48;2;81;255;226m▄\x1b[0m\x1b[38;2;117;255;247;48;2;81;255;245m▄\x1b[0m\x1b[38;2;117;247;255;48;2;81;245;255m▄\x1b[0m\x1b[38;2;117;232;255;48;2;81;226;255m▄\x1b[0m\x1b[38;2;117;217;255;48;2;81;207;255m▄\x1b[0m\x1b[38;2;117;202;255;48;2;81;188;255m▄\x1b[0m\x1b[38;2;117;187;255;48;2;81;169;255m▄\x1b[0m\x1b[38;2;117;172;255;48;2;81;150;255m▄\x1b[0m\x1b[38;2;117;157;255;48;2;81;132;255m▄\x1b[0m\x1b[38;2;117;142;255;48;2;81;113;255m▄\x1b[0m\x1b[38;2;117;127;255;48;2;81;94;255m▄\x1b[0m\x1b[38;2;122;117;255;48;2;87;81;255m▄\x1b[0m\x1b[38;2;137;117;255;48;2;106;81;255m▄\x1b[0m\x1b[38;2;152;117;255;48;2;125;81;255m▄\x1b[0m\x1b[38;2;167;117;255;48;2;144;81;255m▄\x1b[0m\x1b[38;2;182;117;255;48;2;163;81;255m▄\x1b[0m\x1b[38;2;197;117;255;48;2;182;81;255m▄\x1b[0m\x1b[38;2;212;117;255;48;2;201;81;255m▄\x1b[0m\x1b[38;2;227;117;255;48;2;220;81;255m▄\x1b[0m\x1b[38;2;242;117;255;48;2;239;81;255m▄\x1b[0m\x1b[38;2;255;117;252;48;2;255;81;251m▄\x1b[0m\x1b[38;2;255;117;237;48;2;255;81;232m▄\x1b[0m\x1b[38;2;255;117;222;48;2;255;81;214m▄\x1b[0m\x1b[38;2;255;117;207;48;2;255;81;195m▄\x1b[0m\x1b[38;2;255;117;192;48;2;255;81;176m▄\x1b[0m\x1b[38;2;255;117;177;48;2;255;81;157m▄\x1b[0m\x1b[38;2;255;117;162;48;2;255;81;138m▄\x1b[0m\x1b[38;2;255;117;147;48;2;255;81;119m▄\x1b[0m\x1b[38;2;255;117;132;48;2;255;81;100m▄\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Styles \x1b[0m\x1b[1;31m \x1b[0mAll ansi styles: \x1b[1mbold\x1b[0m, \x1b[2mdim\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[4munderline\x1b[0m, \x1b[9mstrikethrough\x1b[0m, \x1b[7mreverse\x1b[0m, and even \n \x1b[5mblink\x1b[0m. \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Text \x1b[0m\x1b[1;31m \x1b[0mWord wrap text. Justify \x1b[32mleft\x1b[0m, \x1b[33mcenter\x1b[0m, \x1b[34mright\x1b[0m or \x1b[31mfull\x1b[0m. \n \n \x1b[32mLorem ipsum dolor \x1b[0m \x1b[33m Lorem ipsum dolor \x1b[0m \x1b[34m Lorem ipsum dolor\x1b[0m \x1b[31mLorem\x1b[0m\x1b[31m \x1b[0m\x1b[31mipsum\x1b[0m\x1b[31m \x1b[0m\x1b[31mdolor\x1b[0m\x1b[31m \x1b[0m\x1b[31msit\x1b[0m \n \x1b[32msit amet, \x1b[0m \x1b[33m sit amet, \x1b[0m \x1b[34m sit amet,\x1b[0m \x1b[31mamet,\x1b[0m\x1b[31m \x1b[0m\x1b[31mconsectetur\x1b[0m \n \x1b[32mconsectetur \x1b[0m \x1b[33m consectetur \x1b[0m \x1b[34m consectetur\x1b[0m \x1b[31madipiscing\x1b[0m\x1b[31m \x1b[0m\x1b[31melit.\x1b[0m \n \x1b[32madipiscing elit. \x1b[0m \x1b[33m adipiscing elit. \x1b[0m \x1b[34m adipiscing elit.\x1b[0m \x1b[31mQuisque\x1b[0m\x1b[31m \x1b[0m\x1b[31min\x1b[0m\x1b[31m \x1b[0m\x1b[31mmetus\x1b[0m\x1b[31m \x1b[0m\x1b[31msed\x1b[0m \n \x1b[32mQuisque in metus sed\x1b[0m \x1b[33mQuisque in metus sed\x1b[0m \x1b[34mQuisque in metus sed\x1b[0m \x1b[31msapien\x1b[0m\x1b[31m \x1b[0m\x1b[31multricies\x1b[0m \n \x1b[32msapien ultricies \x1b[0m \x1b[33m sapien ultricies \x1b[0m \x1b[34m sapien ultricies\x1b[0m \x1b[31mpretium\x1b[0m\x1b[31m \x1b[0m\x1b[31ma\x1b[0m\x1b[31m \x1b[0m\x1b[31mat\x1b[0m\x1b[31m \x1b[0m\x1b[31mjusto.\x1b[0m \n \x1b[32mpretium a at justo. \x1b[0m \x1b[33mpretium a at justo. \x1b[0m \x1b[34m pretium a at justo.\x1b[0m \x1b[31mMaecenas\x1b[0m\x1b[31m \x1b[0m\x1b[31mluctus\x1b[0m\x1b[31m \x1b[0m\x1b[31mvelit\x1b[0m \n \x1b[32mMaecenas luctus \x1b[0m \x1b[33m Maecenas luctus \x1b[0m \x1b[34m Maecenas luctus\x1b[0m \x1b[31met auctor maximus.\x1b[0m \n \x1b[32mvelit et auctor \x1b[0m \x1b[33m velit et auctor \x1b[0m \x1b[34m velit et auctor\x1b[0m \n \x1b[32mmaximus. \x1b[0m \x1b[33m maximus. \x1b[0m \x1b[34m maximus.\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Asian \x1b[0m\x1b[1;31m \x1b[0m🇨🇳 该库支持中文,日文和韩文文本! \n\x1b[1;31m \x1b[0m\x1b[1;31m language \x1b[0m\x1b[1;31m \x1b[0m🇯🇵 ライブラリは中国語、日本語、韓国語のテキストをサポートしています \n\x1b[1;31m \x1b[0m\x1b[1;31m support \x1b[0m\x1b[1;31m \x1b[0m🇰🇷 이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다 \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Markup \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;35mRich\x1b[0m supports a simple \x1b[3mbbcode\x1b[0m like \x1b[1mmarkup\x1b[0m for \x1b[33mcolor\x1b[0m, \x1b[4mstyle\x1b[0m, and emoji! 👍 🍎 🐜 🐻 … \n 🚌 \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Tables \x1b[0m\x1b[1;31m \x1b[0m\x1b[1m \x1b[0m\x1b[1;32mDate\x1b[0m\x1b[1m \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1;34mTitle\x1b[0m\x1b[1m \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1;36mProduction Budget\x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1m \x1b[0m\x1b[1;35mBox Office\x1b[0m\x1b[1m \x1b[0m \n ───────────────────────────────────────────────────────────────────────────────────── \n \x1b[32m \x1b[0m\x1b[32mDec 20, 2019\x1b[0m\x1b[32m \x1b[0m \x1b[34m \x1b[0m\x1b[34mStar Wars: The Rise of \x1b[0m\x1b[34m \x1b[0m \x1b[36m \x1b[0m\x1b[36m $275,000,000\x1b[0m\x1b[36m \x1b[0m \x1b[35m \x1b[0m\x1b[35m $375,126,118\x1b[0m\x1b[35m \x1b[0m \n \x1b[34m \x1b[0m\x1b[34mSkywalker \x1b[0m\x1b[34m \x1b[0m \n \x1b[2;32m \x1b[0m\x1b[2;32mMay 25, 2018\x1b[0m\x1b[2;32m \x1b[0m \x1b[2;34m \x1b[0m\x1b[1;2;34mSolo\x1b[0m\x1b[2;34m: A Star Wars Story \x1b[0m\x1b[2;34m \x1b[0m \x1b[2;36m \x1b[0m\x1b[2;36m $275,000,000\x1b[0m\x1b[2;36m \x1b[0m \x1b[2;35m \x1b[0m\x1b[2;35m $393,151,347\x1b[0m\x1b[2;35m \x1b[0m \n \x1b[32m \x1b[0m\x1b[32mDec 15, 2017\x1b[0m\x1b[32m \x1b[0m \x1b[34m \x1b[0m\x1b[34mStar Wars Ep. VIII: The Last \x1b[0m\x1b[34m \x1b[0m \x1b[36m \x1b[0m\x1b[36m $262,000,000\x1b[0m\x1b[36m \x1b[0m \x1b[35m \x1b[0m\x1b[1;35m$1,332,539,889\x1b[0m\x1b[35m \x1b[0m \n \x1b[34m \x1b[0m\x1b[34mJedi \x1b[0m\x1b[34m \x1b[0m \n \x1b[2;32m \x1b[0m\x1b[2;32mMay 19, 1999\x1b[0m\x1b[2;32m \x1b[0m \x1b[2;34m \x1b[0m\x1b[2;34mStar Wars Ep. \x1b[0m\x1b[1;2;34mI\x1b[0m\x1b[2;34m: \x1b[0m\x1b[2;3;34mThe phantom \x1b[0m\x1b[2;34m \x1b[0m\x1b[2;34m \x1b[0m \x1b[2;36m \x1b[0m\x1b[2;36m $115,000,000\x1b[0m\x1b[2;36m \x1b[0m \x1b[2;35m \x1b[0m\x1b[2;35m$1,027,044,677\x1b[0m\x1b[2;35m \x1b[0m \n \x1b[2;34m \x1b[0m\x1b[2;3;34mMenace\x1b[0m\x1b[2;34m \x1b[0m\x1b[2;34m \x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Syntax \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 1 \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mdef\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34miter_last\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mIterable\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m[\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mT\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m]\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m-\x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m>\x1b[0m \x1b[1m{\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31mhighlighting\x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 2 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;230;219;116;48;2;39;40;34m"""Iterate and generate a tuple w\x1b[0m \x1b[2;32m│ \x1b[0m\x1b[32m\'foo\'\x1b[0m: \x1b[1m[\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m & \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 3 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1;34m3.1427\x1b[0m, \n\x1b[1;31m \x1b[0m\x1b[1;31m pretty \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 4 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mtry\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1m(\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m printing \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 5 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mnext\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_va\x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m\'Paul Atriedies\'\x1b[0m, \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 6 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mexcept\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34mStopIteration\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m\'Vladimir Harkonnen\'\x1b[0m, \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 7 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mreturn\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m\'Thufir Haway\'\x1b[0m \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 8 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mfor\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34min\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1m)\x1b[0m \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 9 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mFalse\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ \x1b[0m\x1b[1m]\x1b[0m, \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m10 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ \x1b[0m\x1b[32m\'atomic\'\x1b[0m: \x1b[1m(\x1b[0m\x1b[3;91mFalse\x1b[0m, \x1b[3;92mTrue\x1b[0m, \x1b[3;35mNone\x1b[0m\x1b[1m)\x1b[0m \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m11 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mTrue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[1m}\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Markdown \x1b[0m\x1b[1;31m \x1b[0m\x1b[36m# Markdown\x1b[0m ╔═══════════════════════════════════════╗ \n ║ \x1b[1mMarkdown\x1b[0m ║ \n \x1b[36mSupports much of the *markdown*, \x1b[0m ╚═══════════════════════════════════════╝ \n \x1b[36m__syntax__!\x1b[0m \n Supports much of the \x1b[3mmarkdown\x1b[0m, \x1b[1msyntax\x1b[0m! \n \x1b[36m- Headers\x1b[0m \n \x1b[36m- Basic formatting: **bold**, *italic*, \x1b[0m \x1b[1;33m • \x1b[0mHeaders \n \x1b[36m`code`\x1b[0m \x1b[1;33m • \x1b[0mBasic formatting: \x1b[1mbold\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[97;40mcode\x1b[0m \n \x1b[36m- Block quotes\x1b[0m \x1b[1;33m • \x1b[0mBlock quotes \n \x1b[36m- Lists, and more...\x1b[0m \x1b[1;33m • \x1b[0mLists, and more... \n \x1b[36m \x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m +more! \x1b[0m\x1b[1;31m \x1b[0mProgress bars, columns, styled logging handler, tracebacks, etc... \n\x1b[1;31m \x1b[0m \n' \ No newline at end of file +expected = "\x1b[3m Rich features \x1b[0m\n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Colors \x1b[0m\x1b[1;31m \x1b[0m✓ \x1b[1;32m4-bit color\x1b[0m \x1b[38;2;86;0;0;48;2;51;0;0m▄\x1b[0m\x1b[38;2;86;9;0;48;2;51;5;0m▄\x1b[0m\x1b[38;2;86;18;0;48;2;51;11;0m▄\x1b[0m\x1b[38;2;86;28;0;48;2;51;16;0m▄\x1b[0m\x1b[38;2;86;37;0;48;2;51;22;0m▄\x1b[0m\x1b[38;2;86;47;0;48;2;51;27;0m▄\x1b[0m\x1b[38;2;86;56;0;48;2;51;33;0m▄\x1b[0m\x1b[38;2;86;66;0;48;2;51;38;0m▄\x1b[0m\x1b[38;2;86;75;0;48;2;51;44;0m▄\x1b[0m\x1b[38;2;86;85;0;48;2;51;50;0m▄\x1b[0m\x1b[38;2;78;86;0;48;2;46;51;0m▄\x1b[0m\x1b[38;2;69;86;0;48;2;40;51;0m▄\x1b[0m\x1b[38;2;59;86;0;48;2;35;51;0m▄\x1b[0m\x1b[38;2;50;86;0;48;2;29;51;0m▄\x1b[0m\x1b[38;2;40;86;0;48;2;24;51;0m▄\x1b[0m\x1b[38;2;31;86;0;48;2;18;51;0m▄\x1b[0m\x1b[38;2;22;86;0;48;2;12;51;0m▄\x1b[0m\x1b[38;2;12;86;0;48;2;7;51;0m▄\x1b[0m\x1b[38;2;3;86;0;48;2;1;51;0m▄\x1b[0m\x1b[38;2;0;86;6;48;2;0;51;3m▄\x1b[0m\x1b[38;2;0;86;15;48;2;0;51;9m▄\x1b[0m\x1b[38;2;0;86;25;48;2;0;51;14m▄\x1b[0m\x1b[38;2;0;86;34;48;2;0;51;20m▄\x1b[0m\x1b[38;2;0;86;44;48;2;0;51;25m▄\x1b[0m\x1b[38;2;0;86;53;48;2;0;51;31m▄\x1b[0m\x1b[38;2;0;86;63;48;2;0;51;37m▄\x1b[0m\x1b[38;2;0;86;72;48;2;0;51;42m▄\x1b[0m\x1b[38;2;0;86;81;48;2;0;51;48m▄\x1b[0m\x1b[38;2;0;81;86;48;2;0;48;51m▄\x1b[0m\x1b[38;2;0;72;86;48;2;0;42;51m▄\x1b[0m\x1b[38;2;0;63;86;48;2;0;37;51m▄\x1b[0m\x1b[38;2;0;53;86;48;2;0;31;51m▄\x1b[0m\x1b[38;2;0;44;86;48;2;0;25;51m▄\x1b[0m\x1b[38;2;0;34;86;48;2;0;20;51m▄\x1b[0m\x1b[38;2;0;25;86;48;2;0;14;51m▄\x1b[0m\x1b[38;2;0;15;86;48;2;0;9;51m▄\x1b[0m\x1b[38;2;0;6;86;48;2;0;3;51m▄\x1b[0m\x1b[38;2;3;0;86;48;2;1;0;51m▄\x1b[0m\x1b[38;2;12;0;86;48;2;7;0;51m▄\x1b[0m\x1b[38;2;22;0;86;48;2;12;0;51m▄\x1b[0m\x1b[38;2;31;0;86;48;2;18;0;51m▄\x1b[0m\x1b[38;2;40;0;86;48;2;24;0;51m▄\x1b[0m\x1b[38;2;50;0;86;48;2;29;0;51m▄\x1b[0m\x1b[38;2;59;0;86;48;2;35;0;51m▄\x1b[0m\x1b[38;2;69;0;86;48;2;40;0;51m▄\x1b[0m\x1b[38;2;78;0;86;48;2;46;0;51m▄\x1b[0m\x1b[38;2;86;0;85;48;2;51;0;50m▄\x1b[0m\x1b[38;2;86;0;75;48;2;51;0;44m▄\x1b[0m\x1b[38;2;86;0;66;48;2;51;0;38m▄\x1b[0m\x1b[38;2;86;0;56;48;2;51;0;33m▄\x1b[0m\x1b[38;2;86;0;47;48;2;51;0;27m▄\x1b[0m\x1b[38;2;86;0;37;48;2;51;0;22m▄\x1b[0m\x1b[38;2;86;0;28;48;2;51;0;16m▄\x1b[0m\x1b[38;2;86;0;18;48;2;51;0;11m▄\x1b[0m\x1b[38;2;86;0;9;48;2;51;0;5m▄\x1b[0m \n ✓ \x1b[1;34m8-bit color\x1b[0m \x1b[38;2;158;0;0;48;2;122;0;0m▄\x1b[0m\x1b[38;2;158;17;0;48;2;122;13;0m▄\x1b[0m\x1b[38;2;158;34;0;48;2;122;26;0m▄\x1b[0m\x1b[38;2;158;51;0;48;2;122;40;0m▄\x1b[0m\x1b[38;2;158;68;0;48;2;122;53;0m▄\x1b[0m\x1b[38;2;158;86;0;48;2;122;66;0m▄\x1b[0m\x1b[38;2;158;103;0;48;2;122;80;0m▄\x1b[0m\x1b[38;2;158;120;0;48;2;122;93;0m▄\x1b[0m\x1b[38;2;158;137;0;48;2;122;106;0m▄\x1b[0m\x1b[38;2;158;155;0;48;2;122;120;0m▄\x1b[0m\x1b[38;2;143;158;0;48;2;111;122;0m▄\x1b[0m\x1b[38;2;126;158;0;48;2;97;122;0m▄\x1b[0m\x1b[38;2;109;158;0;48;2;84;122;0m▄\x1b[0m\x1b[38;2;91;158;0;48;2;71;122;0m▄\x1b[0m\x1b[38;2;74;158;0;48;2;57;122;0m▄\x1b[0m\x1b[38;2;57;158;0;48;2;44;122;0m▄\x1b[0m\x1b[38;2;40;158;0;48;2;31;122;0m▄\x1b[0m\x1b[38;2;22;158;0;48;2;17;122;0m▄\x1b[0m\x1b[38;2;5;158;0;48;2;4;122;0m▄\x1b[0m\x1b[38;2;0;158;11;48;2;0;122;8m▄\x1b[0m\x1b[38;2;0;158;28;48;2;0;122;22m▄\x1b[0m\x1b[38;2;0;158;45;48;2;0;122;35m▄\x1b[0m\x1b[38;2;0;158;63;48;2;0;122;48m▄\x1b[0m\x1b[38;2;0;158;80;48;2;0;122;62m▄\x1b[0m\x1b[38;2;0;158;97;48;2;0;122;75m▄\x1b[0m\x1b[38;2;0;158;114;48;2;0;122;89m▄\x1b[0m\x1b[38;2;0;158;132;48;2;0;122;102m▄\x1b[0m\x1b[38;2;0;158;149;48;2;0;122;115m▄\x1b[0m\x1b[38;2;0;149;158;48;2;0;115;122m▄\x1b[0m\x1b[38;2;0;132;158;48;2;0;102;122m▄\x1b[0m\x1b[38;2;0;114;158;48;2;0;89;122m▄\x1b[0m\x1b[38;2;0;97;158;48;2;0;75;122m▄\x1b[0m\x1b[38;2;0;80;158;48;2;0;62;122m▄\x1b[0m\x1b[38;2;0;63;158;48;2;0;48;122m▄\x1b[0m\x1b[38;2;0;45;158;48;2;0;35;122m▄\x1b[0m\x1b[38;2;0;28;158;48;2;0;22;122m▄\x1b[0m\x1b[38;2;0;11;158;48;2;0;8;122m▄\x1b[0m\x1b[38;2;5;0;158;48;2;4;0;122m▄\x1b[0m\x1b[38;2;22;0;158;48;2;17;0;122m▄\x1b[0m\x1b[38;2;40;0;158;48;2;31;0;122m▄\x1b[0m\x1b[38;2;57;0;158;48;2;44;0;122m▄\x1b[0m\x1b[38;2;74;0;158;48;2;57;0;122m▄\x1b[0m\x1b[38;2;91;0;158;48;2;71;0;122m▄\x1b[0m\x1b[38;2;109;0;158;48;2;84;0;122m▄\x1b[0m\x1b[38;2;126;0;158;48;2;97;0;122m▄\x1b[0m\x1b[38;2;143;0;158;48;2;111;0;122m▄\x1b[0m\x1b[38;2;158;0;155;48;2;122;0;120m▄\x1b[0m\x1b[38;2;158;0;137;48;2;122;0;106m▄\x1b[0m\x1b[38;2;158;0;120;48;2;122;0;93m▄\x1b[0m\x1b[38;2;158;0;103;48;2;122;0;80m▄\x1b[0m\x1b[38;2;158;0;86;48;2;122;0;66m▄\x1b[0m\x1b[38;2;158;0;68;48;2;122;0;53m▄\x1b[0m\x1b[38;2;158;0;51;48;2;122;0;40m▄\x1b[0m\x1b[38;2;158;0;34;48;2;122;0;26m▄\x1b[0m\x1b[38;2;158;0;17;48;2;122;0;13m▄\x1b[0m \n ✓ \x1b[1;35mTruecolor (16.7 million)\x1b[0m \x1b[38;2;229;0;0;48;2;193;0;0m▄\x1b[0m\x1b[38;2;229;25;0;48;2;193;21;0m▄\x1b[0m\x1b[38;2;229;50;0;48;2;193;42;0m▄\x1b[0m\x1b[38;2;229;75;0;48;2;193;63;0m▄\x1b[0m\x1b[38;2;229;100;0;48;2;193;84;0m▄\x1b[0m\x1b[38;2;229;125;0;48;2;193;105;0m▄\x1b[0m\x1b[38;2;229;150;0;48;2;193;126;0m▄\x1b[0m\x1b[38;2;229;175;0;48;2;193;147;0m▄\x1b[0m\x1b[38;2;229;200;0;48;2;193;169;0m▄\x1b[0m\x1b[38;2;229;225;0;48;2;193;190;0m▄\x1b[0m\x1b[38;2;208;229;0;48;2;176;193;0m▄\x1b[0m\x1b[38;2;183;229;0;48;2;155;193;0m▄\x1b[0m\x1b[38;2;158;229;0;48;2;133;193;0m▄\x1b[0m\x1b[38;2;133;229;0;48;2;112;193;0m▄\x1b[0m\x1b[38;2;108;229;0;48;2;91;193;0m▄\x1b[0m\x1b[38;2;83;229;0;48;2;70;193;0m▄\x1b[0m\x1b[38;2;58;229;0;48;2;49;193;0m▄\x1b[0m\x1b[38;2;33;229;0;48;2;28;193;0m▄\x1b[0m\x1b[38;2;8;229;0;48;2;7;193;0m▄\x1b[0m\x1b[38;2;0;229;16;48;2;0;193;14m▄\x1b[0m\x1b[38;2;0;229;41;48;2;0;193;35m▄\x1b[0m\x1b[38;2;0;229;66;48;2;0;193;56m▄\x1b[0m\x1b[38;2;0;229;91;48;2;0;193;77m▄\x1b[0m\x1b[38;2;0;229;116;48;2;0;193;98m▄\x1b[0m\x1b[38;2;0;229;141;48;2;0;193;119m▄\x1b[0m\x1b[38;2;0;229;166;48;2;0;193;140m▄\x1b[0m\x1b[38;2;0;229;191;48;2;0;193;162m▄\x1b[0m\x1b[38;2;0;229;216;48;2;0;193;183m▄\x1b[0m\x1b[38;2;0;216;229;48;2;0;183;193m▄\x1b[0m\x1b[38;2;0;191;229;48;2;0;162;193m▄\x1b[0m\x1b[38;2;0;166;229;48;2;0;140;193m▄\x1b[0m\x1b[38;2;0;141;229;48;2;0;119;193m▄\x1b[0m\x1b[38;2;0;116;229;48;2;0;98;193m▄\x1b[0m\x1b[38;2;0;91;229;48;2;0;77;193m▄\x1b[0m\x1b[38;2;0;66;229;48;2;0;56;193m▄\x1b[0m\x1b[38;2;0;41;229;48;2;0;35;193m▄\x1b[0m\x1b[38;2;0;16;229;48;2;0;14;193m▄\x1b[0m\x1b[38;2;8;0;229;48;2;7;0;193m▄\x1b[0m\x1b[38;2;33;0;229;48;2;28;0;193m▄\x1b[0m\x1b[38;2;58;0;229;48;2;49;0;193m▄\x1b[0m\x1b[38;2;83;0;229;48;2;70;0;193m▄\x1b[0m\x1b[38;2;108;0;229;48;2;91;0;193m▄\x1b[0m\x1b[38;2;133;0;229;48;2;112;0;193m▄\x1b[0m\x1b[38;2;158;0;229;48;2;133;0;193m▄\x1b[0m\x1b[38;2;183;0;229;48;2;155;0;193m▄\x1b[0m\x1b[38;2;208;0;229;48;2;176;0;193m▄\x1b[0m\x1b[38;2;229;0;225;48;2;193;0;190m▄\x1b[0m\x1b[38;2;229;0;200;48;2;193;0;169m▄\x1b[0m\x1b[38;2;229;0;175;48;2;193;0;147m▄\x1b[0m\x1b[38;2;229;0;150;48;2;193;0;126m▄\x1b[0m\x1b[38;2;229;0;125;48;2;193;0;105m▄\x1b[0m\x1b[38;2;229;0;100;48;2;193;0;84m▄\x1b[0m\x1b[38;2;229;0;75;48;2;193;0;63m▄\x1b[0m\x1b[38;2;229;0;50;48;2;193;0;42m▄\x1b[0m\x1b[38;2;229;0;25;48;2;193;0;21m▄\x1b[0m \n ✓ \x1b[1;33mDumb terminals\x1b[0m \x1b[38;2;254;45;45;48;2;255;10;10m▄\x1b[0m\x1b[38;2;254;68;45;48;2;255;36;10m▄\x1b[0m\x1b[38;2;254;91;45;48;2;255;63;10m▄\x1b[0m\x1b[38;2;254;114;45;48;2;255;90;10m▄\x1b[0m\x1b[38;2;254;137;45;48;2;255;117;10m▄\x1b[0m\x1b[38;2;254;159;45;48;2;255;143;10m▄\x1b[0m\x1b[38;2;254;182;45;48;2;255;170;10m▄\x1b[0m\x1b[38;2;254;205;45;48;2;255;197;10m▄\x1b[0m\x1b[38;2;254;228;45;48;2;255;223;10m▄\x1b[0m\x1b[38;2;254;251;45;48;2;255;250;10m▄\x1b[0m\x1b[38;2;235;254;45;48;2;232;255;10m▄\x1b[0m\x1b[38;2;213;254;45;48;2;206;255;10m▄\x1b[0m\x1b[38;2;190;254;45;48;2;179;255;10m▄\x1b[0m\x1b[38;2;167;254;45;48;2;152;255;10m▄\x1b[0m\x1b[38;2;144;254;45;48;2;125;255;10m▄\x1b[0m\x1b[38;2;121;254;45;48;2;99;255;10m▄\x1b[0m\x1b[38;2;99;254;45;48;2;72;255;10m▄\x1b[0m\x1b[38;2;76;254;45;48;2;45;255;10m▄\x1b[0m\x1b[38;2;53;254;45;48;2;19;255;10m▄\x1b[0m\x1b[38;2;45;254;61;48;2;10;255;28m▄\x1b[0m\x1b[38;2;45;254;83;48;2;10;255;54m▄\x1b[0m\x1b[38;2;45;254;106;48;2;10;255;81m▄\x1b[0m\x1b[38;2;45;254;129;48;2;10;255;108m▄\x1b[0m\x1b[38;2;45;254;152;48;2;10;255;134m▄\x1b[0m\x1b[38;2;45;254;175;48;2;10;255;161m▄\x1b[0m\x1b[38;2;45;254;197;48;2;10;255;188m▄\x1b[0m\x1b[38;2;45;254;220;48;2;10;255;214m▄\x1b[0m\x1b[38;2;45;254;243;48;2;10;255;241m▄\x1b[0m\x1b[38;2;45;243;254;48;2;10;241;255m▄\x1b[0m\x1b[38;2;45;220;254;48;2;10;214;255m▄\x1b[0m\x1b[38;2;45;197;254;48;2;10;188;255m▄\x1b[0m\x1b[38;2;45;175;254;48;2;10;161;255m▄\x1b[0m\x1b[38;2;45;152;254;48;2;10;134;255m▄\x1b[0m\x1b[38;2;45;129;254;48;2;10;108;255m▄\x1b[0m\x1b[38;2;45;106;254;48;2;10;81;255m▄\x1b[0m\x1b[38;2;45;83;254;48;2;10;54;255m▄\x1b[0m\x1b[38;2;45;61;254;48;2;10;28;255m▄\x1b[0m\x1b[38;2;53;45;254;48;2;19;10;255m▄\x1b[0m\x1b[38;2;76;45;254;48;2;45;10;255m▄\x1b[0m\x1b[38;2;99;45;254;48;2;72;10;255m▄\x1b[0m\x1b[38;2;121;45;254;48;2;99;10;255m▄\x1b[0m\x1b[38;2;144;45;254;48;2;125;10;255m▄\x1b[0m\x1b[38;2;167;45;254;48;2;152;10;255m▄\x1b[0m\x1b[38;2;190;45;254;48;2;179;10;255m▄\x1b[0m\x1b[38;2;213;45;254;48;2;206;10;255m▄\x1b[0m\x1b[38;2;235;45;254;48;2;232;10;255m▄\x1b[0m\x1b[38;2;254;45;251;48;2;255;10;250m▄\x1b[0m\x1b[38;2;254;45;228;48;2;255;10;223m▄\x1b[0m\x1b[38;2;254;45;205;48;2;255;10;197m▄\x1b[0m\x1b[38;2;254;45;182;48;2;255;10;170m▄\x1b[0m\x1b[38;2;254;45;159;48;2;255;10;143m▄\x1b[0m\x1b[38;2;254;45;137;48;2;255;10;117m▄\x1b[0m\x1b[38;2;254;45;114;48;2;255;10;90m▄\x1b[0m\x1b[38;2;254;45;91;48;2;255;10;63m▄\x1b[0m\x1b[38;2;254;45;68;48;2;255;10;36m▄\x1b[0m \n ✓ \x1b[1;36mAutomatic color conversion\x1b[0m \x1b[38;2;255;117;117;48;2;255;81;81m▄\x1b[0m\x1b[38;2;255;132;117;48;2;255;100;81m▄\x1b[0m\x1b[38;2;255;147;117;48;2;255;119;81m▄\x1b[0m\x1b[38;2;255;162;117;48;2;255;138;81m▄\x1b[0m\x1b[38;2;255;177;117;48;2;255;157;81m▄\x1b[0m\x1b[38;2;255;192;117;48;2;255;176;81m▄\x1b[0m\x1b[38;2;255;207;117;48;2;255;195;81m▄\x1b[0m\x1b[38;2;255;222;117;48;2;255;214;81m▄\x1b[0m\x1b[38;2;255;237;117;48;2;255;232;81m▄\x1b[0m\x1b[38;2;255;252;117;48;2;255;251;81m▄\x1b[0m\x1b[38;2;242;255;117;48;2;239;255;81m▄\x1b[0m\x1b[38;2;227;255;117;48;2;220;255;81m▄\x1b[0m\x1b[38;2;212;255;117;48;2;201;255;81m▄\x1b[0m\x1b[38;2;197;255;117;48;2;182;255;81m▄\x1b[0m\x1b[38;2;182;255;117;48;2;163;255;81m▄\x1b[0m\x1b[38;2;167;255;117;48;2;144;255;81m▄\x1b[0m\x1b[38;2;152;255;117;48;2;125;255;81m▄\x1b[0m\x1b[38;2;137;255;117;48;2;106;255;81m▄\x1b[0m\x1b[38;2;122;255;117;48;2;87;255;81m▄\x1b[0m\x1b[38;2;117;255;127;48;2;81;255;94m▄\x1b[0m\x1b[38;2;117;255;142;48;2;81;255;113m▄\x1b[0m\x1b[38;2;117;255;157;48;2;81;255;132m▄\x1b[0m\x1b[38;2;117;255;172;48;2;81;255;150m▄\x1b[0m\x1b[38;2;117;255;187;48;2;81;255;169m▄\x1b[0m\x1b[38;2;117;255;202;48;2;81;255;188m▄\x1b[0m\x1b[38;2;117;255;217;48;2;81;255;207m▄\x1b[0m\x1b[38;2;117;255;232;48;2;81;255;226m▄\x1b[0m\x1b[38;2;117;255;247;48;2;81;255;245m▄\x1b[0m\x1b[38;2;117;247;255;48;2;81;245;255m▄\x1b[0m\x1b[38;2;117;232;255;48;2;81;226;255m▄\x1b[0m\x1b[38;2;117;217;255;48;2;81;207;255m▄\x1b[0m\x1b[38;2;117;202;255;48;2;81;188;255m▄\x1b[0m\x1b[38;2;117;187;255;48;2;81;169;255m▄\x1b[0m\x1b[38;2;117;172;255;48;2;81;150;255m▄\x1b[0m\x1b[38;2;117;157;255;48;2;81;132;255m▄\x1b[0m\x1b[38;2;117;142;255;48;2;81;113;255m▄\x1b[0m\x1b[38;2;117;127;255;48;2;81;94;255m▄\x1b[0m\x1b[38;2;122;117;255;48;2;87;81;255m▄\x1b[0m\x1b[38;2;137;117;255;48;2;106;81;255m▄\x1b[0m\x1b[38;2;152;117;255;48;2;125;81;255m▄\x1b[0m\x1b[38;2;167;117;255;48;2;144;81;255m▄\x1b[0m\x1b[38;2;182;117;255;48;2;163;81;255m▄\x1b[0m\x1b[38;2;197;117;255;48;2;182;81;255m▄\x1b[0m\x1b[38;2;212;117;255;48;2;201;81;255m▄\x1b[0m\x1b[38;2;227;117;255;48;2;220;81;255m▄\x1b[0m\x1b[38;2;242;117;255;48;2;239;81;255m▄\x1b[0m\x1b[38;2;255;117;252;48;2;255;81;251m▄\x1b[0m\x1b[38;2;255;117;237;48;2;255;81;232m▄\x1b[0m\x1b[38;2;255;117;222;48;2;255;81;214m▄\x1b[0m\x1b[38;2;255;117;207;48;2;255;81;195m▄\x1b[0m\x1b[38;2;255;117;192;48;2;255;81;176m▄\x1b[0m\x1b[38;2;255;117;177;48;2;255;81;157m▄\x1b[0m\x1b[38;2;255;117;162;48;2;255;81;138m▄\x1b[0m\x1b[38;2;255;117;147;48;2;255;81;119m▄\x1b[0m\x1b[38;2;255;117;132;48;2;255;81;100m▄\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Styles \x1b[0m\x1b[1;31m \x1b[0mAll ansi styles: \x1b[1mbold\x1b[0m, \x1b[2mdim\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[4munderline\x1b[0m, \x1b[9mstrikethrough\x1b[0m, \x1b[7mreverse\x1b[0m, and even \n \x1b[5mblink\x1b[0m. \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Text \x1b[0m\x1b[1;31m \x1b[0mWord wrap text. Justify \x1b[32mleft\x1b[0m, \x1b[33mcenter\x1b[0m, \x1b[34mright\x1b[0m or \x1b[31mfull\x1b[0m. \n \n \x1b[32mLorem ipsum dolor \x1b[0m \x1b[33m Lorem ipsum dolor \x1b[0m \x1b[34m Lorem ipsum dolor\x1b[0m \x1b[31mLorem\x1b[0m\x1b[31m \x1b[0m\x1b[31mipsum\x1b[0m\x1b[31m \x1b[0m\x1b[31mdolor\x1b[0m\x1b[31m \x1b[0m\x1b[31msit\x1b[0m \n \x1b[32msit amet, \x1b[0m \x1b[33m sit amet, \x1b[0m \x1b[34m sit amet,\x1b[0m \x1b[31mamet,\x1b[0m\x1b[31m \x1b[0m\x1b[31mconsectetur\x1b[0m \n \x1b[32mconsectetur \x1b[0m \x1b[33m consectetur \x1b[0m \x1b[34m consectetur\x1b[0m \x1b[31madipiscing\x1b[0m\x1b[31m \x1b[0m\x1b[31melit.\x1b[0m \n \x1b[32madipiscing elit. \x1b[0m \x1b[33m adipiscing elit. \x1b[0m \x1b[34m adipiscing elit.\x1b[0m \x1b[31mQuisque\x1b[0m\x1b[31m \x1b[0m\x1b[31min\x1b[0m\x1b[31m \x1b[0m\x1b[31mmetus\x1b[0m\x1b[31m \x1b[0m\x1b[31msed\x1b[0m \n \x1b[32mQuisque in metus sed\x1b[0m \x1b[33mQuisque in metus sed\x1b[0m \x1b[34mQuisque in metus sed\x1b[0m \x1b[31msapien\x1b[0m\x1b[31m \x1b[0m\x1b[31multricies\x1b[0m \n \x1b[32msapien ultricies \x1b[0m \x1b[33m sapien ultricies \x1b[0m \x1b[34m sapien ultricies\x1b[0m \x1b[31mpretium\x1b[0m\x1b[31m \x1b[0m\x1b[31ma\x1b[0m\x1b[31m \x1b[0m\x1b[31mat\x1b[0m\x1b[31m \x1b[0m\x1b[31mjusto.\x1b[0m \n \x1b[32mpretium a at justo. \x1b[0m \x1b[33mpretium a at justo. \x1b[0m \x1b[34m pretium a at justo.\x1b[0m \x1b[31mMaecenas\x1b[0m\x1b[31m \x1b[0m\x1b[31mluctus\x1b[0m\x1b[31m \x1b[0m\x1b[31mvelit\x1b[0m \n \x1b[32mMaecenas luctus \x1b[0m \x1b[33m Maecenas luctus \x1b[0m \x1b[34m Maecenas luctus\x1b[0m \x1b[31met auctor maximus.\x1b[0m \n \x1b[32mvelit et auctor \x1b[0m \x1b[33m velit et auctor \x1b[0m \x1b[34m velit et auctor\x1b[0m \n \x1b[32mmaximus. \x1b[0m \x1b[33m maximus. \x1b[0m \x1b[34m maximus.\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Asian \x1b[0m\x1b[1;31m \x1b[0m🇨🇳 该库支持中文,日文和韩文文本! \n\x1b[1;31m \x1b[0m\x1b[1;31m language \x1b[0m\x1b[1;31m \x1b[0m🇯🇵 ライブラリは中国語、日本語、韓国語のテキストをサポートしています \n\x1b[1;31m \x1b[0m\x1b[1;31m support \x1b[0m\x1b[1;31m \x1b[0m🇰🇷 이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다 \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Markup \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;35mRich\x1b[0m supports a simple \x1b[3mbbcode\x1b[0m like \x1b[1mmarkup\x1b[0m for \x1b[33mcolor\x1b[0m, \x1b[4mstyle\x1b[0m, and emoji! 👍 🍎 🐜 🐻 … \n 🚌 \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Tables \x1b[0m\x1b[1;31m \x1b[0m\x1b[1m \x1b[0m\x1b[1;32mDate\x1b[0m\x1b[1m \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1;34mTitle\x1b[0m\x1b[1m \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1;36mProduction Budget\x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1m \x1b[0m\x1b[1;35mBox Office\x1b[0m\x1b[1m \x1b[0m \n ───────────────────────────────────────────────────────────────────────────────────── \n \x1b[32m \x1b[0m\x1b[32mDec 20, 2019\x1b[0m\x1b[32m \x1b[0m \x1b[34m \x1b[0m\x1b[34mStar Wars: The Rise of \x1b[0m\x1b[34m \x1b[0m \x1b[36m \x1b[0m\x1b[36m $275,000,000\x1b[0m\x1b[36m \x1b[0m \x1b[35m \x1b[0m\x1b[35m $375,126,118\x1b[0m\x1b[35m \x1b[0m \n \x1b[34m \x1b[0m\x1b[34mSkywalker \x1b[0m\x1b[34m \x1b[0m \n \x1b[2;32m \x1b[0m\x1b[2;32mMay 25, 2018\x1b[0m\x1b[2;32m \x1b[0m \x1b[2;34m \x1b[0m\x1b[1;2;34mSolo\x1b[0m\x1b[2;34m: A Star Wars Story \x1b[0m\x1b[2;34m \x1b[0m \x1b[2;36m \x1b[0m\x1b[2;36m $275,000,000\x1b[0m\x1b[2;36m \x1b[0m \x1b[2;35m \x1b[0m\x1b[2;35m $393,151,347\x1b[0m\x1b[2;35m \x1b[0m \n \x1b[32m \x1b[0m\x1b[32mDec 15, 2017\x1b[0m\x1b[32m \x1b[0m \x1b[34m \x1b[0m\x1b[34mStar Wars Ep. VIII: The Last \x1b[0m\x1b[34m \x1b[0m \x1b[36m \x1b[0m\x1b[36m $262,000,000\x1b[0m\x1b[36m \x1b[0m \x1b[35m \x1b[0m\x1b[1;35m$1,332,539,889\x1b[0m\x1b[35m \x1b[0m \n \x1b[34m \x1b[0m\x1b[34mJedi \x1b[0m\x1b[34m \x1b[0m \n \x1b[2;32m \x1b[0m\x1b[2;32mMay 19, 1999\x1b[0m\x1b[2;32m \x1b[0m \x1b[2;34m \x1b[0m\x1b[2;34mStar Wars Ep. \x1b[0m\x1b[1;2;34mI\x1b[0m\x1b[2;34m: \x1b[0m\x1b[2;3;34mThe phantom \x1b[0m\x1b[2;34m \x1b[0m\x1b[2;34m \x1b[0m \x1b[2;36m \x1b[0m\x1b[2;36m $115,000,000\x1b[0m\x1b[2;36m \x1b[0m \x1b[2;35m \x1b[0m\x1b[2;35m$1,027,044,677\x1b[0m\x1b[2;35m \x1b[0m \n \x1b[2;34m \x1b[0m\x1b[2;3;34mMenace\x1b[0m\x1b[2;34m \x1b[0m\x1b[2;34m \x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Syntax \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 1 \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mdef\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34miter_last\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mIterable\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m[\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mT\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m]\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m-\x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m>\x1b[0m \x1b[1m{\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31mhighlighting\x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 2 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;230;219;116;48;2;39;40;34m\"\"\"Iterate and generate a tuple w\x1b[0m \x1b[2;32m│ \x1b[0m\x1b[32m'foo'\x1b[0m: \x1b[1m[\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m & \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 3 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalues\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m)\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1;34m3.1427\x1b[0m, \n\x1b[1;31m \x1b[0m\x1b[1;31m pretty \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 4 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mtry\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1m(\x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m printing \x1b[0m\x1b[1;31m \x1b[0m\x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 5 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mnext\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m(\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_va\x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m'Paul Atriedies'\x1b[0m, \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 6 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mexcept\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;166;226;46;48;2;39;40;34mStopIteration\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m'Vladimir Harkonnen'\x1b[0m, \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 7 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mreturn\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ │ \x1b[0m\x1b[32m'Thufir Haway'\x1b[0m \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 8 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mfor\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34min\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34miter_values\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m:\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ │ \x1b[0m\x1b[1m)\x1b[0m \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m 9 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mFalse\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ \x1b[0m\x1b[1m]\x1b[0m, \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m10 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ │ \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;249;38;114;48;2;39;40;34m=\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mvalue\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[2;32m│ \x1b[0m\x1b[32m'atomic'\x1b[0m: \x1b[1m(\x1b[0m\x1b[3;91mFalse\x1b[0m, \x1b[3;92mTrue\x1b[0m, \x1b[3;35mNone\x1b[0m\x1b[1m)\x1b[0m \n \x1b[1;38;2;227;227;221;48;2;39;40;34m \x1b[0m\x1b[38;2;101;102;96;48;2;39;40;34m11 \x1b[0m\x1b[2;38;2;117;113;94;48;2;39;40;34m│ \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34myield\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;102;217;239;48;2;39;40;34mTrue\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m,\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mprevious_value\x1b[0m\x1b[48;2;39;40;34m \x1b[0m \x1b[1m}\x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m Markdown \x1b[0m\x1b[1;31m \x1b[0m\x1b[36m# Markdown\x1b[0m ╔═══════════════════════════════════════╗ \n ║ \x1b[1mMarkdown\x1b[0m ║ \n \x1b[36mSupports much of the *markdown*, \x1b[0m ╚═══════════════════════════════════════╝ \n \x1b[36m__syntax__!\x1b[0m \n Supports much of the \x1b[3mmarkdown\x1b[0m, \x1b[1msyntax\x1b[0m! \n \x1b[36m- Headers\x1b[0m \n \x1b[36m- Basic formatting: **bold**, *italic*, \x1b[0m \x1b[1;33m • \x1b[0mHeaders \n \x1b[36m`code`\x1b[0m \x1b[1;33m • \x1b[0mBasic formatting: \x1b[1mbold\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[97;40mcode\x1b[0m \n \x1b[36m- Block quotes\x1b[0m \x1b[1;33m • \x1b[0mBlock quotes \n \x1b[36m- Lists, and more...\x1b[0m \x1b[1;33m • \x1b[0mLists, and more... \n \x1b[36m \x1b[0m \n\x1b[1;31m \x1b[0m \n\x1b[1;31m \x1b[0m\x1b[1;31m +more! \x1b[0m\x1b[1;31m \x1b[0mProgress bars, columns, styled logging handler, tracebacks, etc... \n\x1b[1;31m \x1b[0m \n" diff --git a/tests/test_live.py b/tests/test_live.py new file mode 100644 index 000000000..8bf55bed3 --- /dev/null +++ b/tests/test_live.py @@ -0,0 +1,160 @@ +# encoding=utf-8 +import io +import time +from typing import Optional + +# import pytest +from rich.console import Console +from rich.live import Live + + +def create_capture_console( + *, width: int = 60, height: int = 80, force_terminal: Optional[bool] = True +) -> Console: + return Console( + width=width, + height=height, + file=io.StringIO(), + force_terminal=force_terminal, + legacy_windows=False, + color_system=None, # use no color system to reduce complexity of output + ) + + +def test_live_state() -> None: + + with Live("") as live: + assert live._started + live.start() + + assert live.renderable == "" + + assert live._started + live.stop() + assert not live._started + + assert not live._started + + +def test_growing_display() -> None: + console = create_capture_console() + console.begin_capture() + with Live(console=console, auto_refresh=False) as live: + display = "" + for step in range(10): + display += f"Step {step}\n" + live.update(display, refresh=True) + output = console.end_capture() + assert ( + output + == "\x1b[?25lStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h" + ) + + +def test_growing_display_transient() -> None: + console = create_capture_console() + console.begin_capture() + with Live(console=console, auto_refresh=False, transient=True) as live: + display = "" + for step in range(10): + display += f"Step {step}\n" + live.update(display, refresh=True) + output = console.end_capture() + assert ( + output + == "\x1b[?25lStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h\r\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K" + ) + + +def test_growing_display_overflow_ellipsis() -> None: + console = create_capture_console(height=5) + console.begin_capture() + with Live( + console=console, auto_refresh=False, vertical_overflow="ellipsis" + ) as live: + display = "" + for step in range(10): + display += f"Step {step}\n" + live.update(display, refresh=True) + output = console.end_capture() + assert ( + output + == "\x1b[?25lStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h" + ) + + +def test_growing_display_overflow_crop() -> None: + console = create_capture_console(height=5) + console.begin_capture() + with Live(console=console, auto_refresh=False, vertical_overflow="crop") as live: + display = "" + for step in range(10): + display += f"Step {step}\n" + live.update(display, refresh=True) + output = console.end_capture() + assert ( + output + == "\x1b[?25lStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h" + ) + + +def test_growing_display_overflow_visible() -> None: + console = create_capture_console(height=5) + console.begin_capture() + with Live(console=console, auto_refresh=False, vertical_overflow="visible") as live: + display = "" + for step in range(10): + display += f"Step {step}\n" + live.update(display, refresh=True) + output = console.end_capture() + assert ( + output + == "\x1b[?25lStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h" + ) + + +def test_growing_display_autorefresh() -> None: + """Test generating a table but using auto-refresh from threading""" + console = create_capture_console() + + console = create_capture_console(height=5) + console.begin_capture() + with Live(console=console, auto_refresh=True, vertical_overflow="visible") as live: + display = "" + for step in range(10): + display += f"Step {step}\n" + live.update(display) + time.sleep(0.2) + + # no way to truly test w/ multithreading, just make sure it doesn't crash + + +def test_growing_display_console_redirect() -> None: + console = create_capture_console() + console.begin_capture() + with Live(console=console, auto_refresh=False) as live: + display = "" + for step in range(10): + console.print(f"Running step {step}") + display += f"Step {step}\n" + live.update(display, refresh=True) + output = console.end_capture() + assert ( + output + == "\x1b[?25lRunning step 0\n\r\x1b[2KStep 0\n\r\x1b[2K\x1b[1A\x1b[2KRunning step 1\nStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 2\nStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 3\nStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 4\nStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 5\nStep 0\nStep 1\nStep 2\nStep 3\nStep 4\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 6\nStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 7\nStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 8\nStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 9\nStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h" + ) + + +def test_growing_display_file_console() -> None: + console = create_capture_console(force_terminal=False) + console.begin_capture() + with Live(console=console, auto_refresh=False) as live: + display = "" + for step in range(10): + display += f"Step {step}\n" + live.update(display, refresh=True) + output = console.end_capture() + assert ( + output + == "Step 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n" + )