diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/README.md | 5 | ||||
-rw-r--r-- | examples/bars.py | 21 | ||||
-rw-r--r-- | examples/columns.py | 28 | ||||
-rw-r--r-- | examples/downloader.py | 64 | ||||
-rw-r--r-- | examples/exception.py | 36 | ||||
-rw-r--r-- | examples/fullscreen.py | 185 | ||||
-rw-r--r-- | examples/group.py | 9 | ||||
-rw-r--r-- | examples/group2.py | 12 | ||||
-rw-r--r-- | examples/highlighter.py | 20 | ||||
-rw-r--r-- | examples/jobs.py | 31 | ||||
-rw-r--r-- | examples/justify.py | 13 | ||||
-rw-r--r-- | examples/justify2.py | 15 | ||||
-rw-r--r-- | examples/layout.py | 57 | ||||
-rw-r--r-- | examples/link.py | 4 | ||||
-rw-r--r-- | examples/listdir.py | 35 | ||||
-rw-r--r-- | examples/live_progress.py | 45 | ||||
-rw-r--r-- | examples/log.py | 77 | ||||
-rw-r--r-- | examples/overflow.py | 11 | ||||
-rw-r--r-- | examples/padding.py | 5 | ||||
-rw-r--r-- | examples/rainbow.py | 20 | ||||
-rw-r--r-- | examples/screen.py | 21 | ||||
-rw-r--r-- | examples/spinners.py | 23 | ||||
-rw-r--r-- | examples/status.py | 13 | ||||
-rw-r--r-- | examples/table.py | 20 | ||||
-rw-r--r-- | examples/table_movie.py | 197 | ||||
-rw-r--r-- | examples/top_lite_simulator.py | 81 | ||||
-rw-r--r-- | examples/tree.py | 55 |
27 files changed, 1103 insertions, 0 deletions
diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..c7024be --- /dev/null +++ b/examples/README.md @@ -0,0 +1,5 @@ +# Examples + +This directory contains various demonstrations various Rich features. To run them, make sure Rich is installed, then enter `python example.py` on the command line. + +Be sure to check the source! diff --git a/examples/bars.py b/examples/bars.py new file mode 100644 index 0000000..4ce7635 --- /dev/null +++ b/examples/bars.py @@ -0,0 +1,21 @@ +""" + +Use Bar to renderer a sort-of circle. + +""" +import math + +from rich.align import Align +from rich.bar import Bar +from rich.color import Color +from rich import print + + +SIZE = 40 + +for row in range(SIZE): + y = (row / (SIZE - 1)) * 2 - 1 + x = math.sqrt(1 - y * y) + color = Color.from_rgb((1 + y) * 127.5, 0, 0) + bar = Bar(2, width=SIZE * 2, begin=1 - x, end=1 + x, color=color) + print(Align.center(bar)) diff --git a/examples/columns.py b/examples/columns.py new file mode 100644 index 0000000..96962b0 --- /dev/null +++ b/examples/columns.py @@ -0,0 +1,28 @@ +""" +This example shows how to display content in columns. + +The data is pulled from https://randomuser.me +""" + +import json +from urllib.request import urlopen + +from rich.console import Console +from rich.columns import Columns +from rich.panel import Panel + + +def get_content(user): + """Extract text from user dict.""" + country = user["location"]["country"] + name = f"{user['name']['first']} {user['name']['last']}" + return f"[b]{name}[/b]\n[yellow]{country}" + + +console = Console() + + +users = json.loads(urlopen("https://randomuser.me/api/?results=30").read())["results"] +console.print(users, overflow="ignore", crop=False) +user_renderables = [Panel(get_content(user), expand=True) for user in users] +console.print(Columns(user_renderables)) diff --git a/examples/downloader.py b/examples/downloader.py new file mode 100644 index 0000000..5120ed5 --- /dev/null +++ b/examples/downloader.py @@ -0,0 +1,64 @@ +""" +A rudimentary URL downloader (like wget or curl) to demonstrate Rich progress bars. +""" + +from concurrent.futures import ThreadPoolExecutor +from functools import partial +import os.path +import sys +from typing import Iterable +from urllib.request import urlopen + +from rich.progress import ( + BarColumn, + DownloadColumn, + TextColumn, + TransferSpeedColumn, + TimeRemainingColumn, + Progress, + TaskID, +) + + +progress = Progress( + TextColumn("[bold blue]{task.fields[filename]}", justify="right"), + BarColumn(bar_width=None), + "[progress.percentage]{task.percentage:>3.1f}%", + "•", + DownloadColumn(), + "•", + TransferSpeedColumn(), + "•", + TimeRemainingColumn(), +) + + +def copy_url(task_id: TaskID, url: str, path: str) -> None: + """Copy data from a url to a local file.""" + response = urlopen(url) + # This will break if the response doesn't contain content length + progress.update(task_id, total=int(response.info()["Content-length"])) + with open(path, "wb") as dest_file: + progress.start_task(task_id) + for data in iter(partial(response.read, 32768), b""): + dest_file.write(data) + progress.update(task_id, advance=len(data)) + + +def download(urls: Iterable[str], dest_dir: str): + """Download multuple files to the given directory.""" + with progress: + with ThreadPoolExecutor(max_workers=4) as pool: + for url in urls: + filename = url.split("/")[-1] + dest_path = os.path.join(dest_dir, filename) + task_id = progress.add_task("download", filename=filename, start=False) + pool.submit(copy_url, task_id, url, dest_path) + + +if __name__ == "__main__": + # Try with https://releases.ubuntu.com/20.04/ubuntu-20.04.1-desktop-amd64.iso + if sys.argv[1:]: + download(sys.argv[1:], "./") + else: + print("Usage:\n\tpython downloader.py URL1 URL2 URL3 (etc)") diff --git a/examples/exception.py b/examples/exception.py new file mode 100644 index 0000000..6bf2a1e --- /dev/null +++ b/examples/exception.py @@ -0,0 +1,36 @@ +""" +Basic example to show how to print an traceback of an exception +""" +from typing import List, Tuple +from rich.console import Console + +console = Console() + + +def divide_by(number: float, divisor: float) -> float: + """Divide any number by zero.""" + # Will throw a ZeroDivisionError if divisor is 0 + result = number / divisor + return result + + +def divide_all(divides: List[Tuple[float, float]]) -> None: + """Do something impossible every day.""" + try: + for number, divisor in divides: + result = divide_by(number, divisor) + console.print(f"{number} divided by {divisor} is {result}") + except Exception: + console.print_exception(extra_lines=5, show_locals=True) + + +DIVIDES = [ + (1000, 200), + (10000, 500), + (0, 1000000), + (3.1427, 2), + (2 ** 32, 2 ** 16), + (1, 0), +] + +divide_all(DIVIDES) diff --git a/examples/fullscreen.py b/examples/fullscreen.py new file mode 100644 index 0000000..507eed1 --- /dev/null +++ b/examples/fullscreen.py @@ -0,0 +1,185 @@ +""" +Demonstrates a Rich "application" using the Layout and Live classes. + +""" + +from datetime import datetime + +from rich import box +from rich.align import Align +from rich.console import Console, RenderGroup +from rich.layout import Layout +from rich.panel import Panel +from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn +from rich.syntax import Syntax +from rich.table import Table +from rich.text import Text + +console = Console() + + +def make_layout() -> Layout: + """Define the layout.""" + layout = Layout(name="root") + + layout.split( + Layout(name="header", size=3), + Layout(name="main", ratio=1), + Layout(name="footer", size=7), + ) + layout["main"].split( + Layout(name="side"), + Layout(name="body", ratio=2, minimum_size=60), + direction="horizontal", + ) + layout["side"].split(Layout(name="box1"), Layout(name="box2")) + return layout + + +def make_sponsor_message() -> Panel: + """Some example content.""" + sponsor_message = Table.grid(padding=1) + sponsor_message.add_column(style="green", justify="right") + sponsor_message.add_column(no_wrap=True) + sponsor_message.add_row( + "Sponsor me", + "[u blue link=https://github.com/sponsors/willmcgugan]https://github.com/sponsors/willmcgugan", + ) + sponsor_message.add_row( + "Buy me a :coffee:", + "[u blue link=https://ko-fi.com/willmcgugan]https://ko-fi.com/willmcgugan", + ) + sponsor_message.add_row( + "Twitter", + "[u blue link=https://twitter.com/willmcgugan]https://twitter.com/willmcgugan", + ) + sponsor_message.add_row( + "Blog", "[u blue link=https://www.willmcgugan.com]https://www.willmcgugan.com" + ) + + intro_message = Text.from_markup( + """Consider supporting my work via Github Sponsors (ask your company / organization), or buy me a coffee to say thanks. - Will McGugan""" + ) + + message = Table.grid(padding=1) + message.add_column() + message.add_column(no_wrap=True) + message.add_row(intro_message, sponsor_message) + + message_panel = Panel( + Align.center( + RenderGroup(intro_message, "\n", Align.center(sponsor_message)), + vertical="middle", + ), + box=box.ROUNDED, + padding=(1, 2), + title="[b red]Thanks for trying out Rich!", + border_style="bright_blue", + ) + return message_panel + + +class Header: + """Display header with clock.""" + + def __rich__(self) -> Panel: + grid = Table.grid(expand=True) + grid.add_column(justify="center", ratio=1) + grid.add_column(justify="right") + grid.add_row( + "[b]Rich[/b] Layout application", + datetime.now().ctime().replace(":", "[blink]:[/]"), + ) + return Panel(grid, style="white on blue") + + +def make_syntax() -> Syntax: + code = """\ +def ratio_resolve(total: int, edges: List[Edge]) -> List[int]: + sizes = [(edge.size or None) for edge in edges] + + # While any edges haven't been calculated + while any(size is None for size in sizes): + # Get flexible edges and index to map these back on to sizes list + flexible_edges = [ + (index, edge) + for index, (size, edge) in enumerate(zip(sizes, edges)) + if size is None + ] + # Remaining space in total + remaining = total - sum(size or 0 for size in sizes) + if remaining <= 0: + # No room for flexible edges + sizes[:] = [(size or 0) for size in sizes] + break + # Calculate number of characters in a ratio portion + portion = remaining / sum((edge.ratio or 1) for _, edge in flexible_edges) + + # If any edges will be less than their minimum, replace size with the minimum + for index, edge in flexible_edges: + if portion * edge.ratio <= edge.minimum_size: + sizes[index] = edge.minimum_size + break + else: + # Distribute flexible space and compensate for rounding error + # Since edge sizes can only be integers we need to add the remainder + # to the following line + _modf = modf + remainder = 0.0 + for index, edge in flexible_edges: + remainder, size = _modf(portion * edge.ratio + remainder) + sizes[index] = int(size) + break + # Sizes now contains integers only + return cast(List[int], sizes) + """ + syntax = Syntax(code, "python", line_numbers=True) + return syntax + + +job_progress = Progress( + "{task.description}", + SpinnerColumn(), + BarColumn(), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), +) +job_progress.add_task("[green]Cooking") +job_progress.add_task("[magenta]Baking", total=200) +job_progress.add_task("[cyan]Mixing", total=400) + +total = sum(task.total for task in job_progress.tasks) +overall_progress = Progress() +overall_task = overall_progress.add_task("All Jobs", total=int(total)) + +progress_table = Table.grid(expand=True) +progress_table.add_row( + Panel( + overall_progress, + title="Overall Progress", + border_style="green", + padding=(2, 2), + ), + Panel(job_progress, title="[b]Jobs", border_style="red", padding=(1, 2)), +) + + +layout = make_layout() +layout["header"].update(Header()) +layout["body"].update(make_sponsor_message()) +layout["box2"].update(Panel(make_syntax(), border_style="green")) +layout["box1"].update(Panel(layout.tree, border_style="red")) +layout["footer"].update(progress_table) + + +from rich.live import Live +from time import sleep + +with Live(layout, refresh_per_second=10, screen=True): + while not overall_progress.finished: + sleep(0.1) + for job in job_progress.tasks: + if not job.finished: + job_progress.advance(job.id) + + completed = sum(task.completed for task in job_progress.tasks) + overall_progress.update(overall_task, completed=completed) diff --git a/examples/group.py b/examples/group.py new file mode 100644 index 0000000..a61f830 --- /dev/null +++ b/examples/group.py @@ -0,0 +1,9 @@ +from rich import print +from rich.console import RenderGroup +from rich.panel import Panel + +panel_group = RenderGroup( + Panel("Hello", style="on blue"), + Panel("World", style="on red"), +) +print(Panel(panel_group)) diff --git a/examples/group2.py b/examples/group2.py new file mode 100644 index 0000000..13be070 --- /dev/null +++ b/examples/group2.py @@ -0,0 +1,12 @@ +from rich import print +from rich.console import render_group +from rich.panel import Panel + + +@render_group() +def get_panels(): + yield Panel("Hello", style="on blue") + yield Panel("World", style="on red") + + +print(Panel(get_panels())) diff --git a/examples/highlighter.py b/examples/highlighter.py new file mode 100644 index 0000000..a667d0d --- /dev/null +++ b/examples/highlighter.py @@ -0,0 +1,20 @@ +""" +This example demonstrates a simple text highlighter. +""" + +from rich.console import Console +from rich.highlighter import RegexHighlighter +from rich.theme import Theme + + +class EmailHighlighter(RegexHighlighter): + """Apply style to anything that looks like an email.""" + + base_style = "example." + highlights = [r"(?P<email>[\w-]+@([\w-]+\.)+[\w-]+)"] + + +theme = Theme({"example.email": "bold magenta"}) +console = Console(highlighter=EmailHighlighter(), theme=theme) + +console.print("Send funds to money@example.org") diff --git a/examples/jobs.py b/examples/jobs.py new file mode 100644 index 0000000..6286b56 --- /dev/null +++ b/examples/jobs.py @@ -0,0 +1,31 @@ +from time import sleep +from rich.panel import Panel +from rich.progress import Progress + + +JOBS = [100, 150, 25, 70, 110, 90] + +progress = Progress(auto_refresh=False) +master_task = progress.add_task("overall", total=sum(JOBS)) +jobs_task = progress.add_task("jobs") + +progress.console.print( + Panel( + "[bold blue]A demonstration of progress with a current task and overall progress.", + padding=1, + ) +) + +with progress: + for job_no, job in enumerate(JOBS): + progress.log(f"Starting job #{job_no}") + sleep(0.2) + progress.reset(jobs_task, total=job, description=f"job [bold yellow]#{job_no}") + progress.start_task(jobs_task) + for wait in progress.track(range(job), task_id=jobs_task): + sleep(0.01) + progress.advance(master_task, job) + progress.log(f"Job #{job_no} is complete") + progress.log( + Panel(":sparkle: All done! :sparkle:", border_style="green", padding=1) + ) diff --git a/examples/justify.py b/examples/justify.py new file mode 100644 index 0000000..6709882 --- /dev/null +++ b/examples/justify.py @@ -0,0 +1,13 @@ +""" +This example demonstrates the justify argument to print. +""" + +from rich.console import Console + +console = Console(width=20) + +style = "bold white on blue" +console.print("Rich", style=style) +console.print("Rich", style=style, justify="left") +console.print("Rich", style=style, justify="center") +console.print("Rich", style=style, justify="right") diff --git a/examples/justify2.py b/examples/justify2.py new file mode 100644 index 0000000..3b9882a --- /dev/null +++ b/examples/justify2.py @@ -0,0 +1,15 @@ +""" +This example demonstrates the justify argument to print. +""" + +from rich.console import Console +from rich.panel import Panel + +console = Console(width=20) + +style = "bold white on blue" +panel = Panel("Rich", style="on red", expand=False) +console.print(panel, style=style) +console.print(panel, style=style, justify="left") +console.print(panel, style=style, justify="center") +console.print(panel, style=style, justify="right") diff --git a/examples/layout.py b/examples/layout.py new file mode 100644 index 0000000..046476b --- /dev/null +++ b/examples/layout.py @@ -0,0 +1,57 @@ +""" + +Demonstrates a dynamic Layout + +""" + +from datetime import datetime + +from time import sleep + +from rich.align import Align +from rich.console import Console +from rich.layout import Layout +from rich.live import Live +from rich.text import Text + +console = Console() +layout = Layout() + +layout.split( + Layout(name="header", size=1), + Layout(ratio=1, name="main"), + Layout(size=10, name="footer"), +) + +layout["main"].split( + Layout(name="side"), Layout(name="body", ratio=2), direction="horizontal" +) + +layout["side"].split(Layout(), Layout()) + +layout["body"].update( + Align.center( + Text( + """This is a demonstration of rich.Layout\n\nHit Ctrl+C to exit""", + justify="center", + ), + vertical="middle", + ) +) + + +class Clock: + """Renders the time in the center of the screen.""" + + def __rich__(self) -> Text: + return Text(datetime.now().ctime(), style="bold magenta", justify="center") + + +layout["header"].update(Clock()) + +with Live(layout, screen=True) as live: + try: + while True: + sleep(1) + except KeyboardInterrupt: + pass diff --git a/examples/link.py b/examples/link.py new file mode 100644 index 0000000..9773c33 --- /dev/null +++ b/examples/link.py @@ -0,0 +1,4 @@ +from rich import print + +print("If your terminal supports links, the following text should be clickable:") +print("[link=https://www.willmcgugan.com][i]Visit [red]my[/red][/i] [yellow]Blog[/]") diff --git a/examples/listdir.py b/examples/listdir.py new file mode 100644 index 0000000..bffc50a --- /dev/null +++ b/examples/listdir.py @@ -0,0 +1,35 @@ +""" +A very simple `ls` clone. + +If your terminal supports hyperlinks you should be able to launch files by clicking the filename +(usually with cmd / ctrl). + +""" + +import os +import sys + +from rich import print +from rich.columns import Columns +from rich.text import Text + +try: + root_path = sys.argv[1] +except IndexError: + print("Usage: python listdir.py DIRECTORY") +else: + + def make_filename_text(filename): + path = os.path.abspath(os.path.join(root_path, filename)) + text = Text(filename, style="bold blue" if os.path.isdir(path) else "default") + text.stylize(f"link file://{path}") + text.highlight_regex(r"\..*?$", "bold") + return text + + filenames = [ + filename for filename in os.listdir(root_path) if not filename.startswith(".") + ] + filenames.sort(key=lambda filename: filename.lower()) + filename_text = [make_filename_text(filename) for filename in filenames] + columns = Columns(filename_text, equal=True, column_first=True) + print(columns) diff --git a/examples/live_progress.py b/examples/live_progress.py new file mode 100644 index 0000000..7ae1056 --- /dev/null +++ b/examples/live_progress.py @@ -0,0 +1,45 @@ +""" + +Demonstrates the use of multiple Progress instances in a single Live display. + +""" + +from time import sleep + +from rich.live import Live +from rich.panel import Panel +from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn +from rich.table import Table + + +job_progress = Progress( + "{task.description}", + SpinnerColumn(), + BarColumn(), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), +) +job1 = job_progress.add_task("[green]Cooking") +job2 = job_progress.add_task("[magenta]Baking", total=200) +job3 = job_progress.add_task("[cyan]Mixing", total=400) + +total = sum(task.total for task in job_progress.tasks) +overall_progress = Progress() +overall_task = overall_progress.add_task("All Jobs", total=int(total)) + +progress_table = Table.grid() +progress_table.add_row( + Panel.fit( + overall_progress, title="Overall Progress", border_style="green", padding=(2, 2) + ), + Panel.fit(job_progress, title="[b]Jobs", border_style="red", padding=(1, 2)), +) + +with Live(progress_table, refresh_per_second=10): + while not overall_progress.finished: + sleep(0.1) + for job in job_progress.tasks: + if not job.finished: + job_progress.advance(job.id) + + completed = sum(task.completed for task in job_progress.tasks) + overall_progress.update(overall_task, completed=completed) diff --git a/examples/log.py b/examples/log.py new file mode 100644 index 0000000..5fe54e8 --- /dev/null +++ b/examples/log.py @@ -0,0 +1,77 @@ +""" +A simulation of Rich console logging. +""" + +import time +from rich.console import Console +from rich.style import Style +from rich.theme import Theme +from rich.highlighter import RegexHighlighter + + +class RequestHighlighter(RegexHighlighter): + base_style = "req." + highlights = [ + r"^(?P<protocol>\w+) (?P<method>\w+) (?P<path>\S+) (?P<result>\w+) (?P<stats>\[.+\])$", + r"\/(?P<filename>\w+\..{3,4})", + ] + + +theme = Theme( + { + "req.protocol": Style.parse("dim bold green"), + "req.method": Style.parse("bold cyan"), + "req.path": Style.parse("magenta"), + "req.filename": Style.parse("bright_magenta"), + "req.result": Style.parse("yellow"), + "req.stats": Style.parse("dim"), + } +) +console = Console(theme=theme) + +console.log("Server starting...") +console.log("Serving on http://127.0.0.1:8000") + +time.sleep(1) + +request_highlighter = RequestHighlighter() + +console.log( + request_highlighter("HTTP GET /foo/bar/baz/egg.html 200 [0.57, 127.0.0.1:59076]"), +) + +console.log( + request_highlighter( + "HTTP GET /foo/bar/baz/background.jpg 200 [0.57, 127.0.0.1:59076]" + ), +) + + +time.sleep(1) + + +def test_locals(): + foo = (1, 2, 3) + movies = ["Deadpool", "Rise of the Skywalker"] + console = Console() + + console.log( + "[b]JSON[/b] RPC [i]batch[/i]", + [ + {"jsonrpc": "2.0", "method": "sum", "params": [1, 2, 4], "id": "1"}, + {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}, + {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "2"}, + {"foo": "boo"}, + { + "jsonrpc": "2.0", + "method": "foo.get", + "params": {"name": "myself", "enable": False, "grommits": None}, + "id": "5", + }, + {"jsonrpc": "2.0", "method": "get_data", "id": "9"}, + ], + log_locals=True, + ) + + +test_locals() diff --git a/examples/overflow.py b/examples/overflow.py new file mode 100644 index 0000000..5adaa3d --- /dev/null +++ b/examples/overflow.py @@ -0,0 +1,11 @@ +from typing import List +from rich.console import Console, OverflowMethod + +console = Console(width=14) +supercali = "supercalifragilisticexpialidocious" + +overflow_methods: List[OverflowMethod] = ["fold", "crop", "ellipsis"] +for overflow in overflow_methods: + console.rule(overflow) + console.print(supercali, overflow=overflow, style="bold blue") + console.print() diff --git a/examples/padding.py b/examples/padding.py new file mode 100644 index 0000000..b01d274 --- /dev/null +++ b/examples/padding.py @@ -0,0 +1,5 @@ +from rich import print +from rich.padding import Padding + +test = Padding("Hello", (2, 4), style="on blue", expand=False) +print(test) diff --git a/examples/rainbow.py b/examples/rainbow.py new file mode 100644 index 0000000..2121a78 --- /dev/null +++ b/examples/rainbow.py @@ -0,0 +1,20 @@ +""" + +This example demonstrates how to write a custom highlighter. + +""" + +from random import randint + +from rich import print +from rich.highlighter import Highlighter + + +class RainbowHighlighter(Highlighter): + def highlight(self, text): + for index in range(len(text)): + text.stylize(f"color({randint(16, 255)})", index, index + 1) + + +rainbow = RainbowHighlighter() +print(rainbow("I must not fear. Fear is the mind-killer.")) diff --git a/examples/screen.py b/examples/screen.py new file mode 100644 index 0000000..90f1028 --- /dev/null +++ b/examples/screen.py @@ -0,0 +1,21 @@ +""" +Demonstration of Console.screen() +""" + +from time import sleep + +from rich.console import Console +from rich.align import Align +from rich.text import Text +from rich.panel import Panel + +console = Console() + +with console.screen(style="bold white on red") as screen: + for count in range(5, 0, -1): + text = Align.center( + Text.from_markup(f"[blink]Don't Panic![/blink]\n{count}", justify="center"), + vertical="middle", + ) + screen.update(Panel(text)) + sleep(1) diff --git a/examples/spinners.py b/examples/spinners.py new file mode 100644 index 0000000..9daf847 --- /dev/null +++ b/examples/spinners.py @@ -0,0 +1,23 @@ +from time import sleep + +from rich.columns import Columns +from rich.panel import Panel +from rich.live import Live +from rich.text import Text +from rich.spinner import Spinner, SPINNERS + +all_spinners = Columns( + [ + Spinner(spinner_name, text=Text(repr(spinner_name), style="green")) + for spinner_name in sorted(SPINNERS) + ], + column_first=True, + expand=True, +) + +with Live( + Panel(all_spinners, title="Spinners", border_style="blue"), + refresh_per_second=20, +) as live: + while True: + sleep(0.1) diff --git a/examples/status.py b/examples/status.py new file mode 100644 index 0000000..88d1679 --- /dev/null +++ b/examples/status.py @@ -0,0 +1,13 @@ +from time import sleep +from rich.console import Console + +console = Console() +console.print() + +tasks = [f"task {n}" for n in range(1, 11)] + +with console.status("[bold green]Working on tasks...") as status: + while tasks: + task = tasks.pop(0) + sleep(1) + console.log(f"{task} complete") diff --git a/examples/table.py b/examples/table.py new file mode 100644 index 0000000..6ffa7ee --- /dev/null +++ b/examples/table.py @@ -0,0 +1,20 @@ +""" +Demonstrates how to render a table. +""" + +from rich.console import Console +from rich.table import Table + +table = Table(title="Star Wars Movies") + +table.add_column("Released", style="cyan", no_wrap=True) +table.add_column("Title", style="magenta") +table.add_column("Box Office", justify="right", style="green") + +table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690") +table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") +table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889") +table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889") + +console = Console() +console.print(table, justify="center") diff --git a/examples/table_movie.py b/examples/table_movie.py new file mode 100644 index 0000000..eaa35cd --- /dev/null +++ b/examples/table_movie.py @@ -0,0 +1,197 @@ +"""Same as the table_movie.py but uses Live to update""" +import time +from contextlib import contextmanager + +from rich import box +from rich.align import Align +from rich.console import Console +from rich.live import Live +from rich.measure import Measurement +from rich.table import Table +from rich.text import Text + +TABLE_DATA = [ + [ + "May 25, 1977", + "Star Wars Ep. [b]IV[/]: [i]A New Hope", + "$11,000,000", + "$1,554,475", + "$775,398,007", + ], + [ + "May 21, 1980", + "Star Wars Ep. [b]V[/]: [i]The Empire Strikes Back", + "$23,000,000", + "$4,910,483", + "$547,969,004", + ], + [ + "May 25, 1983", + "Star Wars Ep. [b]VI[/b]: [i]Return of the Jedi", + "$32,500,000", + "$23,019,618", + "$475,106,177", + ], + [ + "May 19, 1999", + "Star Wars Ep. [b]I[/b]: [i]The phantom Menace", + "$115,000,000", + "$64,810,870", + "$1,027,044,677", + ], + [ + "May 16, 2002", + "Star Wars Ep. [b]II[/b]: [i]Attack of the Clones", + "$115,000,000", + "$80,027,814", + "$656,695,615", + ], + [ + "May 19, 2005", + "Star Wars Ep. [b]III[/b]: [i]Revenge of the Sith", + "$115,500,000", + "$380,270,577", + "$848,998,877", + ], +] + +console = Console() + +BEAT_TIME = 0.04 + + +@contextmanager +def beat(length: int = 1) -> None: + yield + time.sleep(length * BEAT_TIME) + + +table = Table(show_footer=False) +table_centered = Align.center(table) + +console.clear() + +with Live(table_centered, console=console, screen=False, refresh_per_second=20): + with beat(10): + table.add_column("Release Date", no_wrap=True) + + with beat(10): + table.add_column("Title", Text.from_markup("[b]Total", justify="right")) + + with beat(10): + table.add_column("Budget", "[u]$412,000,000", no_wrap=True) + + with beat(10): + table.add_column("Opening Weekend", "[u]$577,703,455", no_wrap=True) + + with beat(10): + table.add_column("Box Office", "[u]$4,331,212,357", no_wrap=True) + + with beat(10): + table.title = "Star Wars Box Office" + + with beat(10): + table.title = ( + "[not italic]:popcorn:[/] Star Wars Box Office [not italic]:popcorn:[/]" + ) + + with beat(10): + table.caption = "Made with Rich" + + with beat(10): + table.caption = "Made with [b]Rich[/b]" + + with beat(10): + table.caption = "Made with [b magenta not dim]Rich[/]" + + for row in TABLE_DATA: + with beat(10): + table.add_row(*row) + + with beat(10): + table.show_footer = True + + table_width = Measurement.get(console, table, console.width).maximum + + with beat(10): + table.columns[2].justify = "right" + + with beat(10): + table.columns[3].justify = "right" + + with beat(10): + table.columns[4].justify = "right" + + with beat(10): + table.columns[2].header_style = "bold red" + + with beat(10): + table.columns[3].header_style = "bold green" + + with beat(10): + table.columns[4].header_style = "bold blue" + + with beat(10): + table.columns[2].style = "red" + + with beat(10): + table.columns[3].style = "green" + + with beat(10): + table.columns[4].style = "blue" + + with beat(10): + table.columns[0].style = "cyan" + table.columns[0].header_style = "bold cyan" + + with beat(10): + table.columns[1].style = "magenta" + table.columns[1].header_style = "bold magenta" + + with beat(10): + table.columns[2].footer_style = "bright_red" + + with beat(10): + table.columns[3].footer_style = "bright_green" + + with beat(10): + table.columns[4].footer_style = "bright_blue" + + with beat(10): + table.row_styles = ["none", "dim"] + + with beat(10): + table.border_style = "bright_yellow" + + for box in [ + box.SQUARE, + box.MINIMAL, + box.SIMPLE, + box.SIMPLE_HEAD, + ]: + with beat(10): + table.box = box + + with beat(10): + table.pad_edge = False + + original_width = Measurement.get(console, table).maximum + + for width in range(original_width, console.width, 2): + with beat(1): + table.width = width + + for width in range(console.width, original_width, -2): + with beat(1): + table.width = width + + for width in range(original_width, 90, -2): + with beat(1): + table.width = width + + for width in range(90, original_width + 1, 2): + with beat(1): + table.width = width + + with beat(2): + table.width = None diff --git a/examples/top_lite_simulator.py b/examples/top_lite_simulator.py new file mode 100644 index 0000000..e4a10f5 --- /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, screen=True, auto_refresh=False) as live: + while True: + live.update(create_process_table(console.size.height - 4), refresh=True) + time.sleep(1) diff --git a/examples/tree.py b/examples/tree.py new file mode 100644 index 0000000..9927719 --- /dev/null +++ b/examples/tree.py @@ -0,0 +1,55 @@ +""" +Demonstrates how to display a tree of files / directories with the Tree renderable. +""" + +import os +import pathlib +import sys + +from rich import print +from rich.filesize import decimal +from rich.markup import escape +from rich.text import Text +from rich.tree import Tree + + +def walk_directory(directory: pathlib.Path, tree: Tree) -> None: + """Recursively build a Tree with directory contents.""" + # Sort dirs first then by filename + paths = sorted( + pathlib.Path(directory).iterdir(), + key=lambda path: (path.is_file(), path.name.lower()), + ) + for path in paths: + # Remove hidden files + if path.name.startswith("."): + continue + if path.is_dir(): + style = "dim" if path.name.startswith("__") else "" + branch = tree.add( + f"[bold magenta]:open_file_folder: [link file://{path}]{escape(path.name)}", + style=style, + guide_style=style, + ) + walk_directory(path, branch) + else: + text_filename = Text(path.name, "green") + text_filename.highlight_regex(r"\..*$", "bold red") + text_filename.stylize(f"link file://{path}") + file_size = path.stat().st_size + text_filename.append(f" ({decimal(file_size)})", "blue") + icon = "🐍 " if path.suffix == ".py" else "📄 " + tree.add(Text(icon) + text_filename) + + +try: + directory = os.path.abspath(sys.argv[1]) +except IndexError: + print("[b]Usage:[/] python tree.py <DIRECTORY>") +else: + tree = Tree( + f":open_file_folder: [link file://{directory}]{directory}", + guide_style="bold bright_blue", + ) + walk_directory(pathlib.Path(directory), tree) + print(tree) |