resolved RESOLVED PASS · 13 tool calls · 149 s · Textualize/rich
fix table rendering order of box elements
## Type of changes
- [x] Bug fix
- [ ] New feature
- [ ] Documentation / docstrings
- [x] Tests
- [ ] Other
## Checklist
- [x] I've run the latest [black](https://github.com/psf/black) with default args on new code.
- [x] I've updated CHANGELOG.md and CONTRIBUTORS.md where appropriate.
- [x] I've added tests for new code.
- [x] I accept that @willmcgugan may be pedantic in the code review.
## Description
* Previously "mid" box elements appeared in last line of table (footer), while "footer" box elements appeared mid table body. Reversed that order so mid is truly mid, footer is truly footer.
* Tested with:
```
table.box.head_left = '1'
table.box.head_vertical = '2'
table.box.head_right = '3'
table.box.mid_left = '4'
table.box.mid_vertical = '5'
table.box.mid_right = '6'
table.box.foot_left = '7'
table.box.foot_vertical = '8'
table.box.foot_right = '9'
```
After change, correct ordering:
```
+--------------------------------------------------------------------------+
1Date 2Title 2 Production2 Box Office3
|------------ --------------------------------- ------------ --------------|
4Dec 20, 20195Star Wars: The Rise of Skywalker 5$275,000,0005 $375,126,1186
4May 25, 20185Solo: A Star Wars Story 5$275,000,0005 $393,151,3476
7Dec 15, 20178Star Wars Ep. VIII: The Last Jedi8$262,000,0008$1,332,539,8899
+--------------------------------------------------------------------------+
```
* All non-skipped prior automated tests pass. No automated tests added. Unclear that additional test for box ordering would add proportionate value.
| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"table.box\" . | head -20"
} | {"status": "ok", "stdout": "./examples/table_movie.py:172: table.box = box_style\n./rich/box.py:475: table.box = getattr(box, box_name)\n", "stderr": "", "exit_full{"status": "ok", "stdout": "./examples/table_movie.py:172: table.box = box_style\n./rich/box.py:475: table.box = getattr(box, box_name)\n", "stderr": "", "exit_code": 0} |
| 2 | run_command | {
"command": "grep -rn \"mid_left\" . | head -20"
} | {"status": "ok", "stdout": "./rich/box.py:50: self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4)\n./rich/box.py:146: left = self.mid_left\n./richfull{"status": "ok", "stdout": "./rich/box.py:50: self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4)\n./rich/box.py:146: left = self.mid_left\n./rich/table.py:784: _Segment(_box.mid_left, border_style),\n./rich/panel.py:226: line_start = Segment(box.mid_left, border_style)\n", "stderr": "", "exit_code": 0} |
| 3 | run_command | {
"command": "grep -n \"mid_left\" rich/table.py"
} | {"status": "ok", "stdout": "784: _Segment(_box.mid_left, border_style),\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "784: _Segment(_box.mid_left, border_style),\n", "stderr": "", "exit_code": 0} |
| 4 | run_command | {
"command": "sed -n '750,820p' rich/table.py"
} | {"status": "ok", "stdout": " )\n row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells))\n _box = (\n self.box.substitute(\n options, safe=pick_bool(selffull{"status": "ok", "stdout": " )\n row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells))\n _box = (\n self.box.substitute(\n options, safe=pick_bool(self.safe_box, console.safe_box)\n )\n if self.box\n else None\n )\n _box = _box.get_plain_headed_box() if _box and not self.show_header else _box\n\n new_line = Segment.line()\n\n columns = self.columns\n show_header = self.show_header\n show_footer = self.show_footer\n show_edge = self.show_edge\n show_lines = self.show_lines\n leading = self.leading\n\n _Segment = Segment\n if _box:\n box_segments = [\n (\n _Segment(_box.head_left, border_style),\n _Segment(_box.head_right, border_style),\n _Segment(_box.head_vertical, border_style),\n ),\n (\n _Segment(_box.foot_left, border_style),\n _Segment(_box.foot_right, border_style),\n _Segment(_box.foot_vertical, border_style),\n ),\n (\n _Segment(_box.mid_left, border_style),\n _Segment(_box.mid_right, border_style),\n _Segment(_box.mid_vertical, border_style),\n ),\n ]\n if show_edge:\n yield _Segment(_box.get_top(widths), border_style)\n yield new_line\n else:\n box_segments = []\n\n get_row_style = self.get_row_style\n get_style = console.get_style\n\n for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)):\n header_row = first and show_header\n footer_row = last and show_footer\n row = (\n self.rows[index - show_header]\n if (not header_row and not footer_row)\n |
| 5 | run_command | {
"command": "sed -n '760,790p' rich/table.py"
} | {"status": "ok", "stdout": "\n new_line = Segment.line()\n\n columns = self.columns\n show_header = self.show_header\n show_footer = self.show_footer\n show_edgfull{"status": "ok", "stdout": "\n new_line = Segment.line()\n\n columns = self.columns\n show_header = self.show_header\n show_footer = self.show_footer\n show_edge = self.show_edge\n show_lines = self.show_lines\n leading = self.leading\n\n _Segment = Segment\n if _box:\n box_segments = [\n (\n _Segment(_box.head_left, border_style),\n _Segment(_box.head_right, border_style),\n _Segment(_box.head_vertical, border_style),\n ),\n (\n _Segment(_box.foot_left, border_style),\n _Segment(_box.foot_right, border_style),\n _Segment(_box.foot_vertical, border_style),\n ),\n (\n _Segment(_box.mid_left, border_style),\n _Segment(_box.mid_right, border_style),\n _Segment(_box.mid_vertical, border_style),\n ),\n ]\n if show_edge:\n yield _Segment(_box.get_top(widths), border_style)\n", "stderr": "", "exit_code": 0} |
| 6 | run_command | {
"command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/table.py\")\ns = p.read_text()\nold = \"\"\" box_segments = [\n (\n _Segment(_box.head_left, border_style),\n _Segment(_box.head_right, border_style),\n _Segment(_box.head_vertical, border_style),\n ),\n (\n _Segment(_box.foot_left, border_style),\n _Segment(_box.foot_right, border_style),\n _Segment(_box.foot_vertical, border_style),\n ),\n (\n _Segment(_box.mid_left, border_style),\n _Segment(_box.mid_right, border_style),\n _Segment(_box.mid_vertical, border_style),\n ),\n ]\"\"\"\nnew = \"\"\" box_segments = [\n (\n _Segment(_box.head_left, border_style),\n _Segment(_box.head_right, border_style),\n _Segment(_box.head_vertical, border_style),\n ),\n (\n _Segment(_box.mid_left, border_style),\n _Segment(_box.mid_right, border_style),\n _Segment(_box.mid_vertical, border_style),\n ),\n (\n _Segment(_box.foot_left, border_style),\n _Segment(_box.foot_right, border_style),\n _Segment(_box.foot_vertical, border_style),\n ),\n ]\"\"\"\nassert s.count(old) == 1, (\"old text found this many times:\", s.count(old))\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 7 | run_command | {
"command": "python3 - <<'PY'\nfrom rich.console import Console\nfrom rich.table import Table\nfrom rich.box import Box\n\nconsole = Console(force_terminal=True)\ntable = Table(show_header=True, show_footer=True, box=Box())\n\n# Set custom box characters to verify ordering\ntable.box.head_left = '1'\ntable.box.head_vertical = '2'\ntable.box.head_right = '3'\n\ntable.box.mid_left = '4'\ntable.box.mid_vertical = '5'\ntable.box.mid_right = '6'\n\ntable.box.foot_left = '7'\ntable.box.foot_vertical = '8'\ntable.box.foot_right = '9'\n\ntable.add_column(\"Date\")\ntable.add_column(\"Title\")\ntable.add_column(\"Production\")\ntable.add_column(\"Box Office\")\n\ntable.add_row(\"Dec 20, 2019\", \"Star Wars: The Rise of Skywalker\", \"$275,000,000\", \"$375,126,118\")\ntable.add_row(\"May 25, 2018\", \"Solo: A Star Wars Story\", \"$275,000,000\", \"$393,151,347\")\ntable.add_row(\"Dec 15, 2017\", \"Star Wars Ep. VIII: The Last Jedi\", \"$262,000,000\", \"$1,332,539,889\")\n\n# We need to trigger footer. Table doesn't have a simple 'add_footer' method.\n# The Table.render method handles footer if show_footer=True and there are footer rows.\n# Actually, Table has a `footer_rows` attribute or similar? \n# Let's check how to add footer rows.\nPY\n"
} | {"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 6, in <module>\nTypeError: Box._full{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 6, in <module>\nTypeError: Box.__init__() missing 1 required positional argument: 'box'\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"<stdin>\", line 6, in <module>\nTypeError: Box.__init__() missing 1 required positional argument: 'box'\n", "exit_code": 1}} |
| 8 | run_command | {
"command": "python3 - <<'PY'\nfrom rich.console import Console\nfrom rich.table import Table\nfrom rich.box import ROUNDED\n\nconsole = Console(force_terminal=True)\ntable = Table(show_header=True, show_footer=True, box=ROUNDED)\n\n# Set custom box characters to verify ordering\n# Since ROUNDED is an instance, we might need to be careful about modifying it globally.\n# Let's create a custom Box if possible, or just modify the instance.\n# Looking at rich/box.py, Box is a class, ROUNDED is an instance.\n# Let's just use a custom class or modify the instance.\n\nclass CustomBox:\n head_left = '1'\n head_vertical = '2'\n head_right = '3'\n mid_left = '4'\n mid_vertical = '5'\n mid_right = '6'\n foot_left = '7'\n foot_vertical = '8'\n foot_right = '9'\n def get_top(self, widths): return \"TOP\"\n def get_bottom(self, widths): return \"BOTTOM\"\n def substitute(self, options, safe=True): return self\n\ntable.box = CustomBox()\n\ntable.add_column(\"Date\")\ntable.add_column(\"Title\")\ntable.add_column(\"Production\")\ntable.add_column(\"Box Office\")\n\ntable.add_row(\"Dec 20, 2019\", \"Star Wars: The Rise of Skywalker\", \"$275,000,000\", \"$375,126,118\")\ntable.add_row(\"May 25, 2018\", \"Solo: A Star Wars Story\", \"$275,000,000\", \"$393,151,347\")\ntable.add_row(\"Dec 15, 2017\", \"Star Wars Ep. VIII: The Last Jedi\", \"$262,000,000\", \"$1,332,539,889\")\n\n# To show footer, we need to add footer rows.\n# Table has a `footer_rows` attribute but it's not usually populated via a method.\n# Let's see how Table handles footers.\n# In rich/table.py, it uses `self.rows` and `self.footer_rows`.\n# Let's just manually add a footer row.\ntable.footer_rows = [(\"Footer 1\", \"Footer 2\", \"Footer 3\", \"Footer 4\")]\n\nconsole.print(table)\nPY\n"
} | {"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 46, in <module>\n File \"/privatfull{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 46, in <module>\n File \"/private/workspace/rich/console.py\", line 1699, in print\n extend(render(renderable, render_options))\n ~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/private/workspace/rich/console.py\", line 1331, in render\n for render_output in iter_render:\n ^^^^^^^^^^^\n File \"/private/workspace/rich/table.py\", line 509, in __rich_console__\n yield from self._render(console, render_options, widths)\n File \"/private/workspace/rich/table.py\", line 896, in _render\n _box.get_row(widths, \"head\", edge=show_edge), border_style\n ^^^^^^^^^^^^\nAttributeError: 'CustomBox' object has no attribute 'get_row'. Did you mean: 'get_top'?\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"<stdin>\", line 46, in <module>\n File \"/private/workspace/rich/console.py\", line 1699, in print\n extend(render(renderable, render_options))\n ~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/private/workspace/rich/console.py\", line 1331, in render\n for render_output in iter_render:\n ^^^^^^^^^^^\n File \"/private/workspace/rich/table.py\", line 509, in __rich_console__\n yield from self._render(console, render_options, widths)\n File \"/private/workspace/rich/table.py\", line 896, in _render\n _box.get_row(widths, \"head\", edge=show_edge), border_style\n ^^^^^^^^^^^^\nAttributeError: 'CustomBox' object has no attribute 'get_row'. Did you mean: 'get_top'?\n", "exit_code": 1}} |
| 9 | run_command | {
"command": "python3 - <<'PY'\nfrom rich.console import Console\nfrom rich.table import Table\nfrom rich.box import Box\n\n# Use a real Box instance to avoid missing methods\n# Box is a class, we can instantiate it with a tuple of lines.\n# Looking at rich/box.py:\n# class Box:\n# def __init__(self, box: Tuple[Tuple[str, ...], ...]):\n# ...\n# self.head_left, _, self.head_vertical, self.head_right = line1\n# ...\n# self.mid_left, _, self.mid_vertical, self.mid_right = line4\n# ...\n# self.foot_left, _, self.foot_vertical, self.foot_right = line7\n\n# Let's create a Box with specific characters.\n# The Box constructor expects a tuple of 7 tuples.\n# line1: head_left, head_top, head_vertical, head_right\n# line2: head_bottom_left, head_bottom_top, head_bottom_vertical, head_bottom_right\n# line3: mid_top_left, mid_top_top, mid_top_vertical, mid_top_right\n# line4: mid_left, mid_top, mid_vertical, mid_right\n# line5: mid_bottom_left, mid_bottom_top, mid_bottom_vertical, mid_bottom_right\n# line6: foot_top_left, foot_top_top, foot_top_vertical, foot_top_right\n# line7: foot_left, foot_bottom, foot_vertical, foot_right\n\ncustom_box_chars = (\n ('1', ' ', '2', '3'), # line 1\n (' ', ' ', ' ', ' '), # line 2\n (' ', ' ', ' ', ' '), # line 3\n ('4', ' ', '5', '6'), # line 4\n (' ', ' ', ' ', ' '), # line 5\n (' ', ' ', ' ', ' '), # line 6\n ('7', ' ', '8', '9'), # line 7\n)\nbox = Box(custom_box_chars)\n\nconsole = Console(force_terminal=True)\ntable = Table(show_header=True, show_footer=True, box=box)\n\ntable.add_column(\"Date\")\ntable.add_column(\"Title\")\ntable.add_column(\"Production\")\ntable.add_column(\"Box Office\")\n\ntable.add_row(\"Dec 20, 2019\", \"Star Wars: The Rise of Skywalker\", \"$275,000,000\", \"$375,126,118\")\ntable.add_row(\"May 25, 2018\", \"Solo: A Star Wars Story\", \"$275,000,000\", \"$393,151,347\")\ntable.add_row(\"Dec 15, 2017\", \"Star Wars Ep. VIII: The Last Jedi\", \"$262,000,000\", \"$1,332,539,889\")\n\ntable.footer_rows = [(\"Footer 1\", \"Footer 2\", \"Footer 3\", \"Footer 4\")]\n\nconsole.print(table)\nPY\n"
} | {"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 36, in <module>\n File \"/privatfull{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 36, in <module>\n File \"/private/workspace/rich/box.py\", line 36, in __init__\n line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines()\n ^^^^^^^^^^^^^^\nAttributeError: 'tuple' object has no attribute 'splitlines'\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"<stdin>\", line 36, in <module>\n File \"/private/workspace/rich/box.py\", line 36, in __init__\n line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines()\n ^^^^^^^^^^^^^^\nAttributeError: 'tuple' object has no attribute 'splitlines'\n", "exit_code": 1}} |
| 10 | run_command | {
"command": "sed -n '20,50p' rich/box.py"
} | {"status": "ok", "stdout": " \u2502 \u2502\u2502 head\n \u251c\u2500\u253c\u2524 head_row\n \u2502 \u2502\u2502 mid\n \u251c\u2500\u253c\u2524 row\n \u251c\u250full{"status": "ok", "stdout": " \u2502 \u2502\u2502 head\n \u251c\u2500\u253c\u2524 head_row\n \u2502 \u2502\u2502 mid\n \u251c\u2500\u253c\u2524 row\n \u251c\u2500\u253c\u2524 foot_row\n \u2502 \u2502\u2502 foot\n \u2514\u2500\u2534\u2518 bottom\n\n Args:\n box (str): Characters making up box.\n ascii (bool, optional): True if this box uses ascii characters only. Default is False.\n \"\"\"\n\n def __init__(self, box: str, *, ascii: bool = False) -> None:\n self._box = box\n self.ascii = ascii\n line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines()\n # top\n self.top_left, self.top, self.top_divider, self.top_right = iter(line1)\n # head\n self.head_left, _, self.head_vertical, self.head_right = iter(line2)\n # head_row\n (\n self.head_row_left,\n self.head_row_horizontal,\n self.head_row_cross,\n self.head_row_right,\n ) = iter(line3)\n\n # mid\n self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4)\n", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "python3 - <<'PY'\nfrom rich.console import Console\nfrom rich.table import Table\nfrom rich.box import Box\n\ncustom_box_chars = (\n \"1234\", # line 1 (top)\n \"1234\", # line 2 (head)\n \"1234\", # line 3 (head_row)\n \"4567\", # line 4 (mid)\n \"1234\", # line 5 (mid_row)\n \"1234\", # line 6 (foot_row)\n \"7890\", # line 7 (foot)\n \"1234\", # line 8 (bottom)\n)\nbox_str = \"\\n\".join(custom_box_chars)\nbox = Box(box_str)\n\nconsole = Console(force_terminal=True)\ntable = Table(show_header=True, show_footer=True, box=box)\n\ntable.add_column(\"Date\")\ntable.add_column(\"Title\")\ntable.add_column(\"Production\")\ntable.add_column(\"Box Office\")\n\ntable.add_row(\"Dec 20, 2019\", \"Star Wars: The Rise of Skywalker\", \"$275,000,000\", \"$375,126,118\")\ntable.add_row(\"May 25, 2018\", \"Solo: A Star Wars Story\", \"$275,000,000\", \"$393,151,347\")\ntable.add_row(\"Dec 15, 2017\", \"Star Wars Ep. VIII: The Last Jedi\", \"$262,000,000\", \"$1,332,539,889\")\n\ntable.footer_rows = [(\"Footer 1\", \"Footer 2\", \"Footer 3\", \"Footer 4\")]\n\nconsole.print(table)\nPY\n"
} | {"status": "ok", "stdout": "12222222222222232222222222222222222222222222222322222222222222322222222222222224\n1 Date 3 Title 3 Production 3 Box Office 4\n122222full{"status": "ok", "stdout": "12222222222222232222222222222222222222222222222322222222222222322222222222222224\n1 Date 3 Title 3 Production 3 Box Office 4\n12222222222222232222222222222222222222222222222322222222222222322222222222222224\n4 Dec 20, 2019 6 Star Wars: The Rise of 6 $275,000,000 6 $375,126,118 7\n4 6 Skywalker 6 6 7\n4 May 25, 2018 6 Solo: A Star Wars Story 6 $275,000,000 6 $393,151,347 7\n4 Dec 15, 2017 6 Star Wars Ep. VIII: The Last 6 $262,000,000 6 $1,332,539,889 7\n4 6 Jedi 6 6 7\n12222222222222232222222222222222222222222222222322222222222222322222222222222224\n7 9 9 9 0\n12222222222222232222222222222222222222222222222322222222222222322222222222222224\n", "stderr": "", "exit_code": 0} |
| 12 | run_command | {
"command": "git diff | head -20"
} | {"status": "ok", "stdout": "diff --git a/rich/table.py b/rich/table.py\nindex fe4054cf..44e457be 100644\n--- a/rich/table.py\n+++ b/rich/table.py\n@@ -775,16 +7full{"status": "ok", "stdout": "diff --git a/rich/table.py b/rich/table.py\nindex fe4054cf..44e457be 100644\n--- a/rich/table.py\n+++ b/rich/table.py\n@@ -775,16 +775,16 @@ class Table(JupyterMixin):\n _Segment(_box.head_right, border_style),\n _Segment(_box.head_vertical, border_style),\n ),\n- (\n- _Segment(_box.foot_left, border_style),\n- _Segment(_box.foot_right, border_style),\n- _Segment(_box.foot_vertical, border_style),\n- ),\n (\n _Segment(_box.mid_left, border_style),\n _Segment(_box.mid_right, border_style),\n _Segment(_box.mid_vertical, border_style),\n ),\n+ (\n+ _Segment(_box.foot_left, border_style),\n", "stderr": "", "exit_code": 0} |
| 13 | run_command | {
"command": "submit_patch"
} | {"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/basfull{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: submit_patch: command not found\n", "exit_code": 127}} |
| 14 | submit_patch | {} | {"status": "ok", "patch_size": 1099, "files_changed": 1}full{"status": "ok", "patch_size": 1099, "files_changed": 1} |
diff --git a/rich/table.py b/rich/table.py
index fe4054cf..44e457be 100644
--- a/rich/table.py
+++ b/rich/table.py
@@ -775,16 +775,16 @@ class Table(JupyterMixin):
_Segment(_box.head_right, border_style),
_Segment(_box.head_vertical, border_style),
),
- (
- _Segment(_box.foot_left, border_style),
- _Segment(_box.foot_right, border_style),
- _Segment(_box.foot_vertical, border_style),
- ),
(
_Segment(_box.mid_left, border_style),
_Segment(_box.mid_right, border_style),
_Segment(_box.mid_vertical, border_style),
),
+ (
+ _Segment(_box.foot_left, border_style),
+ _Segment(_box.foot_right, border_style),
+ _Segment(_box.foot_vertical, border_style),
+ ),
]
if show_edge:
yield _Segment(_box.get_top(widths), border_style)
................... [100%] 19 passed in 0.12s