From ad8523ebaff42cfa193833032dab22f2d0a9cc11 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 1 Jun 2020 16:08:06 +0100 Subject: [PATCH] listdir --- CHANGELOG.md | 10 ++++++++++ examples/columns.py | 6 ++++++ examples/downloader.py | 2 -- examples/highlighter.py | 4 ++++ examples/just.py | 4 ++++ examples/listdir.py | 31 +++++++++++++++++++++++++++---- examples/log.py | 2 -- examples/rainbow.py | 6 ++++++ examples/table.py | 4 +--- rich/columns.py | 39 +++++++++++++++++++++++++++++---------- rich/console.py | 4 ---- rich/table.py | 14 +++++++++++++- tests/test_columns.py | 6 +++++- 13 files changed, 105 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20dd1688..38fb9ec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Changed defaults of Table.grid +- Polished listdir.py example + +### Added + +- Added width argument to Columns + +### Fixed + +- Fixed for `columns_first` argument in Columns +- Fixed incorrect padding in columns with fixed width ## [1.3.0] - 2020-05-31 diff --git a/examples/columns.py b/examples/columns.py index 01d72dea..c721a594 100644 --- a/examples/columns.py +++ b/examples/columns.py @@ -1,3 +1,9 @@ +""" +This example shows how to display content in columns. + +The data is pulled from https://randomuser.me +""" + import json from urllib.request import urlopen diff --git a/examples/downloader.py b/examples/downloader.py index 90fb786d..33e43139 100644 --- a/examples/downloader.py +++ b/examples/downloader.py @@ -1,7 +1,5 @@ """ - A rudimentary URL downloader (like wget or curl) to demonstrate Rich progress bars. - """ from concurrent.futures import ThreadPoolExecutor diff --git a/examples/highlighter.py b/examples/highlighter.py index 077508fa..a667d0d6 100644 --- a/examples/highlighter.py +++ b/examples/highlighter.py @@ -1,3 +1,7 @@ +""" +This example demonstrates a simple text highlighter. +""" + from rich.console import Console from rich.highlighter import RegexHighlighter from rich.theme import Theme diff --git a/examples/just.py b/examples/just.py index 420455d6..67098820 100644 --- a/examples/just.py +++ b/examples/just.py @@ -1,3 +1,7 @@ +""" +This example demonstrates the justify argument to print. +""" + from rich.console import Console console = Console(width=20) diff --git a/examples/listdir.py b/examples/listdir.py index 4518318f..befa6acc 100644 --- a/examples/listdir.py +++ b/examples/listdir.py @@ -1,12 +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 -if len(sys.argv) < 2: - print("Usage: python columns.py DIRECTORY") +try: + root_path = sys.argv[1] +except IndexError: + print("Usage: python listdir.py DIRECTORY") else: - directory = os.listdir(sys.argv[1]) - columns = Columns(directory, equal=True, expand=True) + + 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_all(f"link file://{path}") + text.highlight_regex(r"\..*?$", "bold") + return text + + filenames = [ + filename for filename in os.listdir(sys.argv[1]) 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, padding=(0, 1)) print(columns) diff --git a/examples/log.py b/examples/log.py index d0179014..5fe54e85 100644 --- a/examples/log.py +++ b/examples/log.py @@ -1,7 +1,5 @@ """ - A simulation of Rich console logging. - """ import time diff --git a/examples/rainbow.py b/examples/rainbow.py index e547ae11..bf7841fa 100644 --- a/examples/rainbow.py +++ b/examples/rainbow.py @@ -1,3 +1,9 @@ +""" + +This example demonstrates how to write a custom highlighter. + +""" + from random import randint from rich import print diff --git a/examples/table.py b/examples/table.py index 394fc315..8ee1e8e8 100644 --- a/examples/table.py +++ b/examples/table.py @@ -1,7 +1,5 @@ """ - -Render a rich table. - +Demonstrates how to render a table. """ from rich.console import Console diff --git a/rich/columns.py b/rich/columns.py index e06ebe01..1fb7fa2e 100644 --- a/rich/columns.py +++ b/rich/columns.py @@ -1,4 +1,5 @@ from collections import defaultdict +from itertools import chain from operator import itemgetter from typing import Dict, Iterable, List, Optional, Tuple @@ -15,6 +16,7 @@ class Columns(JupyterMixin): Args: renderables (Iterable[RenderableType]): Any number of Rich renderables (including str), + width (int, optional): The desired width of the columns, or None to auto detect. Defaults to None. padding (PaddingDimensions, optional): Optional padding around cells. Defaults to (0, 1). expand (bool, optional): Expand columns to full width. Defaults to False. equal (bool, optional): Arrange in to equal sized columns. Defaults to False. @@ -26,12 +28,14 @@ class Columns(JupyterMixin): self, renderables: Iterable[RenderableType], padding: PaddingDimensions = (0, 1), + width: int = None, expand: bool = False, equal: bool = False, column_first: bool = False, right_to_left: bool = False, ) -> None: self.renderables = list(renderables) + self.width = width self.padding = padding self.expand = expand self.equal = equal @@ -46,7 +50,8 @@ class Columns(JupyterMixin): render_str(renderable) if isinstance(renderable, str) else renderable for renderable in self.renderables ] - + if not renderables: + return _top, right, _bottom, left = Padding.unpack(self.padding) width_padding = max(left, right) max_width = options.max_width @@ -58,6 +63,8 @@ class Columns(JupyterMixin): get_measurement(console, renderable, max_width).maximum for renderable in renderables ] + if self.equal: + renderable_widths = [max(renderable_widths)] * len(renderable_widths) def iter_renderables( column_count: int, @@ -65,19 +72,26 @@ class Columns(JupyterMixin): item_count = len(renderables) if self.column_first: width_renderables = list(zip(renderable_widths, renderables)) + column_lengths: List[int] = [item_count // column_count] * column_count for col_no in range(item_count % column_count): column_lengths[col_no] += 1 - row = 0 - col = 0 - for _ in width_renderables: - yield width_renderables[row * column_count + col] + + row_count = (item_count + column_count - 1) // column_count + cells = [[-1] * column_count for _ in range(row_count)] + row = col = 0 + for index in range(item_count): + cells[row][col] = index column_lengths[col] -= 1 if column_lengths[col]: row += 1 else: col += 1 row = 0 + for index in chain.from_iterable(cells): + if index == -1: + break + yield width_renderables[index] else: yield from zip(renderable_widths, renderables) # Pad odd elements with spaces @@ -88,12 +102,16 @@ class Columns(JupyterMixin): table = Table.grid(padding=self.padding, collapse_padding=True, pad_edge=False) table.expand = self.expand - if self.equal: - maximum_column = max(renderable_widths) - column_count = max_width // maximum_column + if self.width is not None: + column_count = max_width // self.width for _ in range(column_count): - table.add_column(ratio=1) + table.add_column(width=self.width) else: + if self.equal: + table.expand = True + # for _ in range(column_count): + # table.add_column(ratio=1) + while column_count > 1: widths.clear() column_no = 0 @@ -102,6 +120,7 @@ class Columns(JupyterMixin): total_width = sum(widths.values()) + width_padding * ( len(widths) - 1 ) + table.width = total_width if total_width > max_width: column_count = len(widths) - 1 break @@ -109,6 +128,7 @@ class Columns(JupyterMixin): column_no = (column_no + 1) % column_count else: break + column_count = max(column_count, 1) add_row = table.add_row @@ -143,4 +163,3 @@ if __name__ == "__main__": # pragma: no cover columns.right_to_left = True console.rule() console.print(columns) - print(len(files)) diff --git a/rich/console.py b/rich/console.py index 52154aa5..8b328736 100644 --- a/rich/console.py +++ b/rich/console.py @@ -194,10 +194,6 @@ class ConsoleDimensions(NamedTuple): def _is_jupyter() -> bool: """Check if we're running in a Jupyter notebook.""" - try: - from IPython.display import display - except ImportError: - return False try: get_ipython # type: ignore except NameError: diff --git a/rich/table.py b/rich/table.py index 2efc044e..34e867cd 100644 --- a/rich/table.py +++ b/rich/table.py @@ -374,8 +374,10 @@ class Table(JupyterMixin): widths = [_range.minimum or 1 for _range in width_ranges] else: widths = [_range.maximum or 1 for _range in width_ranges] + padding_width = self.padding[1] + self.padding[3] if self.expand: + ratios = [col.ratio or 0 for col in columns if col.flexible] if any(ratios): fixed_widths = [ @@ -465,11 +467,21 @@ class Table(JupyterMixin): for first, last, (style, renderable) in loop_first_last(raw_cells): yield _Cell(style, add_padding(renderable, first, last)) + def _get_padding_width(self, column_index) -> int: + """Get extra width from padding.""" + _, pad_right, _, pad_left = self.padding + if self.collapse_padding: + if column_index != 0: + pad_left = max(0, pad_right - pad_left) + return pad_left + pad_right + def _measure_column( self, console: "Console", column_index: int, column: Column, max_width: int ) -> Measurement: """Get the minimum and maximum width of the column.""" - padding_width = self.padding[1] + self.padding[3] + + padding_width = self._get_padding_width(column_index) + if column.width is not None: # Fixed width column return Measurement( diff --git a/tests/test_columns.py b/tests/test_columns.py index d33e6fa1..a6d737cb 100644 --- a/tests/test_columns.py +++ b/tests/test_columns.py @@ -42,12 +42,16 @@ def render(): columns.expand = True console.print(columns) console.print() + columns.width = 16 + console.print(columns) + console.print() + render_result = console.file.getvalue() return render_result def test_render(): - expected = "Ursus americanus American buffalo Bison bison American crow \nCorvus brachyrhynchos American marten Martes americana American racer \nColuber constrictor American woodcock Scolopax minor Anaconda (unidentified)\nEunectes sp. Andean goose Chloephaga melanoptera Ant \nAnteater, australian spiny Tachyglossus aculeatus Anteater, giant Myrmecophaga tridactyla\n\nUrsus americanus Corvus brachyrhynchos Coluber constrictor Eunectes sp. \nAnteater, australian spiny American buffalo American marten American woodcock \nAndean goose Tachyglossus aculeatus Bison bison Martes americana \nScolopax minor Chloephaga melanoptera Anteater, giant American crow \nAmerican racer Anaconda (unidentified) Ant Myrmecophaga tridactyla\n\nEunectes sp. Coluber constrictor Corvus brachyrhynchos Ursus americanus \nAmerican woodcock American marten American buffalo Anteater, australian spiny\nMartes americana Bison bison Tachyglossus aculeatus Andean goose \nAmerican crow Anteater, giant Chloephaga melanoptera Scolopax minor \nMyrmecophaga tridactyla Ant Anaconda (unidentified) American racer \n\nMartes americana American crow Ursus americanus \nAnt Eunectes sp. American woodcock \nCorvus brachyrhynchos American buffalo Anteater, giant \nAndean goose Scolopax minor American racer \nBison bison Myrmecophaga tridactyla Anteater, australian spiny \nAnaconda (unidentified) Coluber constrictor American marten \n Tachyglossus aculeatus Chloephaga melanoptera \n\n" + expected = "Ursus americanus American buffalo Bison bison American crow \nCorvus brachyrhynchos American marten Martes americana American racer \nColuber constrictor American woodcock Scolopax minor Anaconda (unidentified)\nEunectes sp. Andean goose Chloephaga melanoptera Ant \nAnteater, australian spiny Tachyglossus aculeatus Anteater, giant Myrmecophaga tridactyla\n\nUrsus americanus American marten Scolopax minor Ant \nAmerican buffalo Martes americana Anaconda (unidentified) Anteater, australian spiny\nBison bison American racer Eunectes sp. Tachyglossus aculeatus \nAmerican crow Coluber constrictor Andean goose Anteater, giant \nCorvus brachyrhynchos American woodcock Chloephaga melanoptera Myrmecophaga tridactyla \n\nAnt Scolopax minor American marten Ursus americanus \nAnteater, australian spiny Anaconda (unidentified) Martes americana American buffalo \nTachyglossus aculeatus Eunectes sp. American racer Bison bison \nAnteater, giant Andean goose Coluber constrictor American crow \nMyrmecophaga tridactyla Chloephaga melanoptera American woodcock Corvus brachyrhynchos\n\nChloephaga melanoptera American racer Ursus americanus \nAnt Coluber constrictor American buffalo \nAnteater, australian spiny American woodcock Bison bison \nTachyglossus aculeatus Scolopax minor American crow \nAnteater, giant Anaconda (unidentified) Corvus brachyrhynchos \nMyrmecophaga tridactyla Eunectes sp. American marten \n Andean goose Martes americana \n\nTachyglossus Chloephaga Anaconda Coluber Corvus Ursus americanus\naculeatus melanoptera (unidentified) constrictor brachyrhynchos \nAnteater, giant Ant Eunectes sp. American American marten American buffalo\n woodcock \nMyrmecophaga Anteater, Andean goose Scolopax minor Martes americana Bison bison \ntridactyla australian spiny \n American racer American crow \n\n" assert render() == expected