From 181973311bfa0d3e40c64eb8b38930861e195988 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 3 Jul 2020 17:17:37 +0100 Subject: [PATCH] table shrinking --- CHANGELOG.md | 14 +++++ examples/table_movie.py | 30 ++++------ pyproject.toml | 2 +- rich/box.py | 23 ++++---- rich/console.py | 3 + rich/measure.py | 8 ++- rich/panel.py | 9 +-- rich/progress.py | 2 +- rich/table.py | 119 +++++++++++++++++----------------------- tests/test_columns.py | 2 +- tests/test_table.py | 2 +- 11 files changed, 102 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3939442..70907036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.0.3] - 2020-07-03 + +### Fixed + +- Fixed edge case with wrapped and overflowed text + +### Changed + +- New algorithm for compressing table that priorities smaller columns + +### Added + +- Added safe_box parameter to Console constructor + ## [3.0.2] - 2020-07-02 ### Added diff --git a/examples/table_movie.py b/examples/table_movie.py index b1e7e803..21deccc1 100644 --- a/examples/table_movie.py +++ b/examples/table_movie.py @@ -54,7 +54,7 @@ TABLE_DATA = [ console = Console() -BEAT_TIME = 0.05 +BEAT_TIME = 0.04 @contextmanager @@ -110,7 +110,7 @@ try: with beat(10): console.print(table, justify="center") - table.caption = "Made with [b magenta]Rich[/]" + table.caption = "Made with [b magenta not dim]Rich[/]" with beat(10): console.print(table, justify="center") @@ -187,25 +187,13 @@ try: with beat(10): console.print(table, justify="center") - for color in [ - "deep_pink4", - "dark_khaki", - "medium_purple2", - "thistle3", - "orange1", - ]: - table.border_style = color - with beat(10): - console.print(table, justify="center") + table.border_style = "bright_yellow" + with beat(10): + console.print(table, justify="center") for box in [ - box.ASCII, - box.ASCII2, - box.DOUBLE, - box.DOUBLE_EDGE, - box.HEAVY_EDGE, - box.MINIMAL_HEAVY_HEAD, - box.MINIMAL_DOUBLE_HEAD, + box.SQUARE, + box.MINIMAL, box.SIMPLE, box.SIMPLE_HEAD, ]: @@ -213,6 +201,10 @@ try: with beat(20): console.print(table, justify="center") + table.pad_edge = False + with beat(20): + console.print(table, justify="center") + original_width = Measurement.get(console, table).maximum for width in range(original_width, console.width, 2): diff --git a/pyproject.toml b/pyproject.toml index b9d19624..8ebacf37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "rich" homepage = "https://github.com/willmcgugan/rich" documentation = "https://rich.readthedocs.io/en/latest/" -version = "3.0.2" +version = "3.0.3" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" authors = ["Will McGugan "] license = "MIT" diff --git a/rich/box.py b/rich/box.py index 6e1dea8c..fe75c0d5 100644 --- a/rich/box.py +++ b/rich/box.py @@ -228,10 +228,10 @@ SIMPLE: Box = Box( """\ -╶──╴ + ── -╶──╴ + ── """ @@ -241,7 +241,7 @@ SIMPLE_HEAD: Box = Box( """\ -╶──╴ + ── @@ -255,10 +255,10 @@ SIMPLE_HEAVY: Box = Box( """\ -╺━━╸ + ━━ -╺━━╸ + ━━ """ @@ -267,14 +267,14 @@ SIMPLE_HEAVY: Box = Box( HORIZONTALS: Box = Box( """\ -╶──╴ + ── -╶──╴ + ── -╶──╴ -╶──╴ + ── + ── -╶──╴ + ── """ ) @@ -412,7 +412,8 @@ if __name__ == "__main__": # pragma: no cover "MINIMAL_HEAVY_HEAD", "MINIMAL_DOUBLE_HEAD", "SIMPLE", - "SIMPLE_HEAD" "SIMPLE_HEAVY", + "SIMPLE_HEAD", + "SIMPLE_HEAVY", "HORIZONTALS", "ROUNDED", "HEAVY", diff --git a/rich/console.py b/rich/console.py index 93daa02e..6119ef89 100644 --- a/rich/console.py +++ b/rich/console.py @@ -277,6 +277,7 @@ class Console: log_time_format (str, optional): Log time format if ``log_time`` is enabled. Defaults to "[%X] ". highlighter (HighlighterType, optional): Default highlighter. legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``. + safe_box (bool, optional): Restrict box options that don't render on legacy Windows. """ def __init__( @@ -301,6 +302,7 @@ class Console: log_time_format: str = "[%X]", highlighter: Optional["HighlighterType"] = ReprHighlighter(), legacy_windows: bool = None, + safe_box: bool = True, ): self.is_jupyter = force_jupyter or _is_jupyter() if self.is_jupyter: @@ -336,6 +338,7 @@ class Console: show_time=log_time, show_path=log_path, time_format=log_time_format ) self.highlighter: HighlighterType = highlighter or _null_highlighter + self.safe_box = safe_box self._record_buffer_lock = threading.RLock() self._thread_locals = ConsoleThreadLocals() diff --git a/rich/measure.py b/rich/measure.py index 91f68d6e..5d2d75d2 100644 --- a/rich/measure.py +++ b/rich/measure.py @@ -22,7 +22,7 @@ class Measurement(NamedTuple): def normalize(self) -> "Measurement": minimum, maximum = self minimum = min(max(0, minimum), maximum) - return Measurement(minimum, max(minimum, maximum)) + return Measurement(max(0, minimum), max(0, max(minimum, maximum))) def with_maximum(self, width: int) -> "Measurement": """Get a RenderableWith where the widths are <= width. @@ -63,8 +63,10 @@ class Measurement(NamedTuple): if is_renderable(renderable): get_console_width = getattr(renderable, "__rich_measure__", None) if get_console_width is not None: - render_width = get_console_width(console, _max_width).with_maximum( - _max_width + render_width = ( + get_console_width(console, _max_width) + .normalize() + .with_maximum(_max_width) ) return render_width.normalize() else: diff --git a/rich/panel.py b/rich/panel.py index b108acaf..8d994714 100644 --- a/rich/panel.py +++ b/rich/panel.py @@ -39,7 +39,7 @@ class Panel(JupyterMixin): renderable: RenderableType, box: Box = ROUNDED, *, - safe_box: bool = True, + safe_box: Optional[bool] = None, expand: bool = True, style: Union[str, Style] = "none", width: Optional[int] = None, @@ -74,11 +74,8 @@ class Panel(JupyterMixin): width = child_width + 2 child_options = options.update(width=child_width) lines = console.render_lines(renderable, child_options) - box = ( - get_safe_box(self.box, console.legacy_windows) - if self.safe_box - else self.box - ) + safe_box = self.safe_box if self.safe_box is not None else console.safe_box + box = get_safe_box(self.box, console.legacy_windows) if safe_box else self.box line_start = Segment(box.mid_left, style) line_end = Segment(f"{box.mid_right}\n", style) yield Segment(box.get_top([width - 2]), style) diff --git a/rich/progress.py b/rich/progress.py index 85c68236..d9f3954f 100644 --- a/rich/progress.py +++ b/rich/progress.py @@ -832,7 +832,7 @@ if __name__ == "__main__": # pragma: no coverage task1 = progress.add_task(" [red]Downloading", total=1000) task2 = progress.add_task(" [green]Processing", total=1000) - # task3 = progress.add_task(" [yellow]Thinking", total=1000, start=False) + task3 = progress.add_task(" [yellow]Thinking", total=1000, start=False) while not progress.finished: progress.update(task1, advance=0.5) diff --git a/rich/table.py b/rich/table.py index 290d9382..3eb4d38c 100644 --- a/rich/table.py +++ b/rich/table.py @@ -98,7 +98,7 @@ class Table(JupyterMixin): caption (Union[str, Text], optional): The table caption rendered below. Defaults to None. width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None. box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`). Defaults to box.HEAVY_HEAD. - safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. + safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1). collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False. pad_edge (bool, optional): Enable padding of edge cells. Defaults to True. @@ -125,7 +125,7 @@ class Table(JupyterMixin): caption: TextType = None, width: int = None, box: Optional[box.Box] = box.HEAVY_HEAD, - safe_box: bool = True, + safe_box: Optional[bool] = None, padding: PaddingDimensions = (0, 1), collapse_padding: bool = False, pad_edge: bool = True, @@ -396,8 +396,7 @@ class Table(JupyterMixin): """Calculate the widths of each column, including padding, not including borders.""" columns = self.columns width_ranges = [ - self._measure_column(console, column, max_width) - for column_index, column in enumerate(columns) + self._measure_column(console, column, max_width) for column in columns ] widths = [_range.maximum or 1 for _range in width_ranges] get_padding_width = self._get_padding_width @@ -422,25 +421,19 @@ class Table(JupyterMixin): widths[index] = fixed_widths[index] + next(iter_flex_widths) table_width = sum(widths) - # Reduce rows that not no_wrap if table_width > max_width: widths = self._collapse_widths( widths, [not column.no_wrap for column in columns], max_width ) - - table_width = sum(widths) - # last resort, reduce columns evenly - if table_width > max_width: - excess_width = table_width - max_width - widths = ratio_reduce( - excess_width, - [width_range.minimum for width_range in width_ranges], - widths, - widths, - ) table_width = sum(widths) - elif table_width < max_width and self.expand: + # last resort, reduce columns evenly + if table_width > max_width: + excess_width = table_width - max_width + widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths) + table_width = sum(widths) + + if table_width < max_width and self.expand: pad_widths = ratio_distribute(max_width - table_width, widths) widths = [_width + pad for _width, pad in zip(widths, pad_widths)] @@ -450,37 +443,42 @@ class Table(JupyterMixin): def _collapse_widths( cls, widths: List[int], wrapable: List[bool], max_width: int ) -> List[int]: - widths = widths[:] + """Reduce widths so that the total is under max_width. + + Args: + widths (List[int]): List of widths. + wrapable (List[bool]): List of booleans that indicate if a column may shrink. + max_width (int): Maximum width to reduce to. + + Returns: + List[int]: A new list of widths. + """ total_width = sum(widths) excess_width = total_width - max_width - while excess_width > 0: - max_column = max( - width if allow_wrap else 0 - for width, allow_wrap in zip(widths, wrapable) - ) - if max_column == 0: - break - try: - second_max_column = max( - width if allow_wrap and width != max_column else 0 - for width, allow_wrap in zip(widths, wrapable) + if any(wrapable): + while total_width and excess_width > 0: + max_column = max( + width for width, allow_wrap in zip(widths, wrapable) if allow_wrap ) - except ValueError: - second_max_column = 0 - column_difference = max_column - second_max_column - ratios = [ - (1 if (width == max_column and allow_wrap) else 0) - for width, allow_wrap in zip(widths, wrapable) - ] - if not any(ratios): - break - - max_reduce = [min(excess_width, column_difference)] * len(widths) - widths = ratio_reduce(excess_width, ratios, max_reduce, widths) - - total_width = sum(widths) - excess_width = total_width - max_width + try: + second_max_column = max( + width if allow_wrap and width != max_column else 0 + for width, allow_wrap in zip(widths, wrapable) + ) + except ValueError: + second_max_column = 0 + column_difference = max_column - second_max_column + ratios = [ + (1 if (width == max_column and allow_wrap) else 0) + for width, allow_wrap in zip(widths, wrapable) + ] + if not any(ratios) or not column_difference: + break + max_reduce = [min(excess_width, column_difference)] * len(widths) + widths = ratio_reduce(excess_width, ratios, max_reduce, widths) + total_width = sum(widths) + excess_width = total_width - max_width return widths def _get_cells(self, column_index: int, column: Column) -> Iterable[_Cell]: @@ -580,10 +578,9 @@ class Table(JupyterMixin): ) ) ) + safe_box = self.safe_box if self.safe_box is not None else console.safe_box _box = ( - box.get_safe_box(self.box, console.legacy_windows) - if self.safe_box - else self.box + box.get_safe_box(self.box, console.legacy_windows) if safe_box else self.box ) # _box = self.box @@ -709,7 +706,7 @@ if __name__ == "__main__": # pragma: no cover table = Table( show_lines=False, row_styles=["red", "green"], - expand=True, + expand=False, show_header=True, show_footer=False, show_edge=True, @@ -718,26 +715,10 @@ if __name__ == "__main__": # pragma: no cover table.add_column("bar") table.add_column("baz") table.add_row("Magnet", "foo" * 20, "bar" * 10, "egg" * 15) - # table.add_row( - # "Magnet", - # "pneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosis", - # "some words", - # ) - # table.add_row( - # "Magnet", - # "pneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosis", - # "some more words", - # )m - # table.add_row( - # "Magnet", - # "pneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosis", - # "small words", - # ) - # table.add_row( - # "Magnet", - # "pneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosis", - # ) - for width in range(180, 20, -5): - c.print(table, width=width) + + for width in range(170, 1, -1): + print(" " * width + "<|") + c = Console(width=width) + c.print(table) c.print("Some more words", width=4, overflow="ellipsis") diff --git a/tests/test_columns.py b/tests/test_columns.py index c241bdaf..b07af7cb 100644 --- a/tests/test_columns.py +++ b/tests/test_columns.py @@ -62,7 +62,7 @@ def render(): def test_render(): - expected = "────────────────────────────────────────────── empty ───────────────────────────────────────────────\n───────────────────────────────────────────── optimal ──────────────────────────────────────────────\nUrsus 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───────────────────────────────────────── optimal, expand ──────────────────────────────────────────\nUrsus 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────────────────────────────────────── columm first, optimal ───────────────────────────────────────\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─────────────────────────────────── column first, right to left ────────────────────────────────────\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────────────────────────────────────── equal columns, expand ───────────────────────────────────────\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─────────────────────────────────────────── fixed width ────────────────────────────────────────────\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 \n spiny \n American racer American crow \n\n" + expected = "────────────────────────────────────────────── empty ───────────────────────────────────────────────\n───────────────────────────────────────────── optimal ──────────────────────────────────────────────\nUrsus 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───────────────────────────────────────── optimal, expand ──────────────────────────────────────────\nUrsus 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────────────────────────────────────── columm first, optimal ───────────────────────────────────────\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─────────────────────────────────── column first, right to left ────────────────────────────────────\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────────────────────────────────────── equal columns, expand ───────────────────────────────────────\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─────────────────────────────────────────── fixed width ────────────────────────────────────────────\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 diff --git a/tests/test_table.py b/tests/test_table.py index ea00c040..2be3c071 100644 --- a/tests/test_table.py +++ b/tests/test_table.py @@ -68,7 +68,7 @@ def render_tables(): def test_render_table(): - expected = "test table\n┏━━━━━━┳┳┓\n┃ foo ┃┃┃\n┡━━━━━━╇╇┩\n│ Ave… │││\n└──────┴┴┘\n table \n caption \n test table \n┏━━━━━━━━━━┳━┳┓\n┃ foo ┃ ┃┃\n┡━━━━━━━━━━╇━╇┩\n│ Averlon… │ ││\n└──────────┴─┴┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━┳━┳┓\n┃ foo ┃ ┃┃\n┡━━━━━━━━━━━━━━━╇━╇┩\n│ Averlongword… │ ││\n└───────────────┴─┴┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━┳━┳┓\n┃ foo ┃ ┃┃\n┡━━━━━━━━━━━━━━━━━━━━╇━╇┩\n│ Averlongwordgoesh… │ ││\n└────────────────────┴─┴┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━┳┓\n┃ foo ┃ b… ┃┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━╇┩\n│ Averlongwordgoeshere │ b… ││\n└──────────────────────┴────┴┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━┓\n┃ foo ┃ bar ┃ ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━┩\n│ Averlongwordgoeshere │ bana… │ │\n└──────────────────────┴───────┴──┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━┓\n┃ foo ┃ bar ┃ b… ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━┩\n│ Averlongwordgoeshere │ banana │ │\n│ │ pancakes │ │\n└──────────────────────┴──────────┴────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━┓\n┃ foo ┃ bar ┃ baz ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━┩\n│ Averlongwordgoeshere │ banana │ │\n│ │ pancakes │ │\n└──────────────────────┴──────────────┴─────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━┓\n┃ foo ┃ bar ┃ baz ┃\n┡━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │\n└───────────────────────┴──────────────────┴─────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━┓\n┃ foo ┃ bar ┃ baz ┃\n┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │\n└──────────────────────────┴────────────────────┴─────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┓\n┃ foo ┃ bar ┃ baz ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │\n└──────────────────────┴─────────────────┴─────┘\n table caption \n test table \n ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┓\n ┃ foo ┃ bar ┃ baz ┃\n ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━┩\n │ Averlongwordgoeshere │ banana pancakes │ │\n └──────────────────────┴─────────────────┴─────┘\n table caption \n test table \n ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┓\n ┃ foo ┃ bar ┃ baz ┃\n ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━┩\n │ Averlongwordgoeshere │ banana pancakes │ │\n └──────────────────────┴─────────────────┴─────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━┓\n┃ foo ┃ bar ┃ baz ┃ ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │ │\n│ Coffee │ │ │ │\n│ Coffee │ Chocolate │ │ cinnamon │\n└──────────────────────┴─────────────────┴─────┴──────────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━┓\n┃ foo ┃ bar ┃ baz ┃ ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │ │\n├──────────────────────┼─────────────────┼─────┼──────────┤\n│ Coffee │ │ │ │\n├──────────────────────┼─────────────────┼─────┼──────────┤\n│ Coffee │ Chocolate │ │ cinnamon │\n└──────────────────────┴─────────────────┴─────┴──────────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━┓\n┃ foo ┃ bar ┃ baz ┃ ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │ │\n├──────────────────────┼─────────────────┼─────┼──────────┤\n│ Coffee │ │ │ │\n├──────────────────────┼─────────────────┼─────┼──────────┤\n│ Coffee │ Chocolate │ │ cinnamon │\n├──────────────────────┼─────────────────┼─────┼──────────┤\n│ total │ │ │ │\n└──────────────────────┴─────────────────┴─────┴──────────┘\n table caption \n test table \n foo ┃ bar ┃ baz ┃ \n━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━\n Averlongwordgoeshere │ banana pancakes │ │ \n──────────────────────┼─────────────────┼─────┼──────────\n Coffee │ │ │ \n──────────────────────┼─────────────────┼─────┼──────────\n Coffee │ Chocolate │ │ cinnamon \n──────────────────────┼─────────────────┼─────┼──────────\n total │ │ │ \n table caption \n test table \n ┃ ┃ ┃ \n foo ┃ bar ┃ baz ┃ \n ┃ ┃ ┃ \n━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━\n │ │ │ \n Averlongwordgoeshere │ banana pancakes │ │ \n │ │ │ \n──────────────────────┼─────────────────┼─────┼──────────\n │ │ │ \n Coffee │ │ │ \n │ │ │ \n──────────────────────┼─────────────────┼─────┼──────────\n │ │ │ \n Coffee │ Chocolate │ │ cinnamon \n │ │ │ \n──────────────────────┼─────────────────┼─────┼──────────\n │ │ │ \n total │ │ │ \n │ │ │ \n table caption \n test table \n ┃ ┃┃\n foo ┃ ┃┃\n ┃ ┃┃\n━━━━━━━━━━━━━━━━╇━╇╇\n │ ││\n Averlongwordg… │ ││\n │ ││\n────────────────┼─┼┼\n │ ││\n Coffee │ ││\n │ ││\n────────────────┼─┼┼\n │ ││\n Coffee │ ││\n │ ││\n────────────────┼─┼┼\n │ ││\n total │ ││\n │ ││\n table caption \n" + expected = "test table\n┏━━━━━━┳┳┓\n┃ foo ┃┃┃\n┡━━━━━━╇╇┩\n│ Ave… │││\n│ │││\n└──────┴┴┘\n table \n caption \n test table \n┏━━━━━━━━━━━┳┳┓\n┃ foo ┃┃┃\n┡━━━━━━━━━━━╇╇┩\n│ Averlong… │││\n│ │││\n└───────────┴┴┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━┳┳┓\n┃ foo ┃┃┃\n┡━━━━━━━━━━━━━━━━╇╇┩\n│ Averlongwordg… │││\n│ │││\n└────────────────┴┴┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━┳┳┓\n┃ foo ┃┃┃\n┡━━━━━━━━━━━━━━━━━━━━━╇╇┩\n│ Averlongwordgoeshe… │││\n│ │││\n└─────────────────────┴┴┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━┳━━┓\n┃ foo ┃ ┃ ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━╇━━┩\n│ Averlongwordgoeshere │ │ │\n│ │ │ │\n└──────────────────────┴──┴──┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━┓\n┃ foo ┃ bar ┃ b… ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━┩\n│ Averlongwordgoeshere │ ba… │ │\n│ │ pa… │ │\n└──────────────────────┴─────┴────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━┓\n┃ foo ┃ bar ┃ baz ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━┩\n│ Averlongwordgoeshere │ banana │ │\n│ │ pancak… │ │\n└──────────────────────┴─────────┴─────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━┓\n┃ foo ┃ bar ┃ baz ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━┩\n│ Averlongwordgoeshere │ banana │ │\n│ │ pancakes │ │\n└──────────────────────┴──────────────┴─────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━┓\n┃ foo ┃ bar ┃ baz ┃\n┡━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │\n└───────────────────────┴──────────────────┴─────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━┓\n┃ foo ┃ bar ┃ baz ┃\n┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │\n└──────────────────────────┴────────────────────┴─────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┓\n┃ foo ┃ bar ┃ baz ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │\n└──────────────────────┴─────────────────┴─────┘\n table caption \n test table \n ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┓\n ┃ foo ┃ bar ┃ baz ┃\n ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━┩\n │ Averlongwordgoeshere │ banana pancakes │ │\n └──────────────────────┴─────────────────┴─────┘\n table caption \n test table \n ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┓\n ┃ foo ┃ bar ┃ baz ┃\n ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━┩\n │ Averlongwordgoeshere │ banana pancakes │ │\n └──────────────────────┴─────────────────┴─────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━┓\n┃ foo ┃ bar ┃ baz ┃ ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │ │\n│ Coffee │ │ │ │\n│ Coffee │ Chocolate │ │ cinnamon │\n└──────────────────────┴─────────────────┴─────┴──────────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━┓\n┃ foo ┃ bar ┃ baz ┃ ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │ │\n├──────────────────────┼─────────────────┼─────┼──────────┤\n│ Coffee │ │ │ │\n├──────────────────────┼─────────────────┼─────┼──────────┤\n│ Coffee │ Chocolate │ │ cinnamon │\n└──────────────────────┴─────────────────┴─────┴──────────┘\n table caption \n test table \n┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━┓\n┃ foo ┃ bar ┃ baz ┃ ┃\n┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━┩\n│ Averlongwordgoeshere │ banana pancakes │ │ │\n├──────────────────────┼─────────────────┼─────┼──────────┤\n│ Coffee │ │ │ │\n├──────────────────────┼─────────────────┼─────┼──────────┤\n│ Coffee │ Chocolate │ │ cinnamon │\n├──────────────────────┼─────────────────┼─────┼──────────┤\n│ total │ │ │ │\n└──────────────────────┴─────────────────┴─────┴──────────┘\n table caption \n test table \n foo ┃ bar ┃ baz ┃ \n━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━\n Averlongwordgoeshere │ banana pancakes │ │ \n──────────────────────┼─────────────────┼─────┼──────────\n Coffee │ │ │ \n──────────────────────┼─────────────────┼─────┼──────────\n Coffee │ Chocolate │ │ cinnamon \n──────────────────────┼─────────────────┼─────┼──────────\n total │ │ │ \n table caption \n test table \n ┃ ┃ ┃ \n foo ┃ bar ┃ baz ┃ \n ┃ ┃ ┃ \n━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━\n │ │ │ \n Averlongwordgoeshere │ banana pancakes │ │ \n │ │ │ \n──────────────────────┼─────────────────┼─────┼──────────\n │ │ │ \n Coffee │ │ │ \n │ │ │ \n──────────────────────┼─────────────────┼─────┼──────────\n │ │ │ \n Coffee │ Chocolate │ │ cinnamon \n │ │ │ \n──────────────────────┼─────────────────┼─────┼──────────\n │ │ │ \n total │ │ │ \n │ │ │ \n table caption \n test table \n ┃┃┃\n foo ┃┃┃\n ┃┃┃\n━━━━━━━━━━━━━━━━━╇╇╇\n │││\n Averlongwordgo… │││\n │││\n │││\n─────────────────┼┼┼\n │││\n Coffee │││\n │││\n─────────────────┼┼┼\n │││\n Coffee │││\n │││\n─────────────────┼┼┼\n │││\n total │││\n │││\n table caption \n" assert render_tables() == expected