lines and markup

This commit is contained in:
Will McGugan 2020-04-13 12:24:13 +01:00
parent 507f54995a
commit 6b59f45c70
6 changed files with 88 additions and 29 deletions

View file

@ -5,6 +5,16 @@ 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).
## [0.8.11] - Unreleased
### Added
- Added Table.show_lines to render lines between rows
### Changed
- Added markup escape with double square brackets
## [0.8.10] - 2020-04-12
### Fixed

View file

@ -1,5 +1,5 @@
.. _console_markup:
py
Console Markup
==============
@ -25,6 +25,14 @@ There is a shorthand for closing a style. If you omit the style name from the cl
print("[bold red]Bold and red[/] not bold or red")
Escaping
~~~~~~~~
Occasionally you may want to print something that Rich would interpret as markup. You can *escape* square brackets by doubling them up. Here's an example::
print("foo[[bar]]")
Rendering Markup
----------------

View file

@ -121,7 +121,7 @@ class Box:
return "".join(parts)
def get_bottom(self, widths: Iterable[int]) -> str:
"""Get the top of a simple box.
"""Get the bottom of a simple box.
Args:
widths (List[int]): Widths of columns.

View file

@ -9,7 +9,7 @@ from .text import Span, Text
from ._emoji_replace import _emoji_replace
re_tags = re.compile(r"(\[\".*?\"\])|(\[.*?\])")
re_tags = re.compile(r"(\[\[)|(\]\])|(\[.*?\])")
def _parse(markup: str) -> Iterable[Tuple[Optional[str], Optional[str]]]:
@ -21,16 +21,14 @@ def _parse(markup: str) -> Iterable[Tuple[Optional[str], Optional[str]]]:
"""
position = 0
for match in re_tags.finditer(markup):
escaped_text, tag_text = match.groups()
escape_open, escape_close, tag_text = match.groups()
start, end = match.span()
if start > position:
yield markup[position:start], None
if tag_text is not None:
yield None, tag_text
else:
yield escaped_text[2:-2], None # type: ignore
yield (escape_open and "[") or (escape_close and "]"), None # type: ignore
position = end
if position < len(markup):
yield markup[position:], None

View file

@ -82,6 +82,7 @@ class Table:
show_header (bool, optional): Show a header row. Defaults to True.
show_footer (bool, optional): Show a footer row. Defaults to False.
show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
show_lines (bool, optional): Draw lines between every row. Defaults to False.
style (Union[str, Style], optional): Default style for the table. Defaults to "none".
row_styles (List[Union, str], optional): Optional list of row styles, if more that one style is give then the styles will alternate. Defaults to None.
header_style (Union[str, Style], optional): Style of the header. Defaults to None.
@ -106,6 +107,7 @@ class Table:
show_header: bool = True,
show_footer: bool = False,
show_edge: bool = True,
show_lines: bool = False,
style: StyleType = "none",
row_styles: Iterable[StyleType] = None,
header_style: StyleType = None,
@ -129,6 +131,7 @@ class Table:
self.show_header = show_header
self.show_footer = show_footer
self.show_edge = show_edge
self.show_lines = show_lines
self.style = style
self.header_style = header_style
self.footer_style = footer_style
@ -139,10 +142,21 @@ class Table:
self.row_styles = list(row_styles or [])
@classmethod
def grid(cls) -> "Table":
"""Get a table with no lines, headers, or footer."""
def grid(cls, padding: PaddingDimensions = 0) -> "Table":
"""Get a table with no lines, headers, or footer.
Args:
padding (PaddingDimensions, optional): Get padding arround cells. Defaults to 0.
Returns:
Table: A table instance.
"""
return cls(
box=None, padding=0, show_header=False, show_footer=False, show_edge=False
box=None,
padding=padding,
show_header=False,
show_footer=False,
show_edge=False,
)
@property
@ -430,10 +444,12 @@ class Table:
table_style = console.get_style(self.style or "")
border_style = table_style + console.get_style(self.border_style or "")
rows: Iterable[Tuple[_Cell, ...]] = zip(
*(
self._get_cells(column_index, column)
for column_index, column in enumerate(self.columns)
rows: List[Tuple[_Cell, ...]] = list(
zip(
*(
self._get_cells(column_index, column)
for column_index, column in enumerate(self.columns)
)
)
)
_box = box.SQUARE if (console.legacy_windows and self.box) else self.box
@ -443,17 +459,21 @@ class Table:
show_header = self.show_header
show_footer = self.show_footer
show_edge = self.show_edge
show_lines = self.show_lines
if _box and show_edge:
yield Segment(_box.get_top(widths), border_style)
yield new_line
_Segment = Segment
get_row_style = self.get_row_style
get_style = console.get_style
for index, (first, last, row) in enumerate(loop_first_last(rows)):
header_row = first and show_header
footer_row = last and show_footer
max_height = 1
cells: List[List[List[Segment]]] = []
if show_header and first:
if header_row or footer_row:
row_style = Style()
else:
row_style = get_style(
@ -480,24 +500,24 @@ class Table:
)
yield new_line
if first:
left = Segment(_box.head_left, border_style)
right = Segment(_box.head_right, border_style)
divider = Segment(_box.head_vertical, border_style)
left = _Segment(_box.head_left, border_style)
right = _Segment(_box.head_right, border_style)
divider = _Segment(_box.head_vertical, border_style)
elif last:
left = Segment(_box.foot_left, border_style)
right = Segment(_box.foot_right, border_style)
divider = Segment(_box.foot_vertical, border_style)
left = _Segment(_box.foot_left, border_style)
right = _Segment(_box.foot_right, border_style)
divider = _Segment(_box.foot_vertical, border_style)
else:
left = Segment(_box.mid_left, border_style)
right = Segment(_box.mid_right, border_style)
divider = Segment(_box.mid_vertical, border_style)
left = _Segment(_box.mid_left, border_style)
right = _Segment(_box.mid_right, border_style)
divider = _Segment(_box.mid_vertical, border_style)
for line_no in range(max_height):
if show_edge:
yield left
for last, rendered_cell in loop_last(cells):
for last_cell, rendered_cell in loop_last(cells):
yield from rendered_cell[line_no]
if not last:
if not last_cell:
yield divider
if show_edge:
yield right
@ -512,9 +532,19 @@ class Table:
_box.get_row(widths, "head", edge=show_edge), border_style
)
yield new_line
if _box and show_lines:
if (
not last
and not (show_footer and index >= len(rows) - 2)
and not (show_header and header_row)
):
yield _Segment(
_box.get_row(widths, "row", edge=show_edge), border_style
)
yield new_line
if _box and show_edge:
yield Segment(_box.get_bottom(widths), border_style)
yield _Segment(_box.get_bottom(widths), border_style)
yield new_line
@ -522,8 +552,16 @@ if __name__ == "__main__": # pragma: no cover
from .console import Console
c = Console()
table = Table(row_styles=["red", "green"], expand=True)
table.add_column("foo", no_wrap=True)
table = Table(
show_lines=False,
row_styles=["red", "green"],
expand=True,
show_header=True,
show_footer=False,
box=box.MINIMAL_DOUBLE_HEAD,
show_edge=True,
)
table.add_column("foo", no_wrap=True, footer="BAR")
table.add_column()
table.add_row(
"Magnet",

View file

@ -202,6 +202,11 @@ class Text:
self._trim_spans()
return self
@property
def spans(self) -> List[Span]:
"""Get a copy of the list of spans."""
return self._spans[:]
def blank_copy(self) -> "Text":
"""Return a new Text instance with copied meta data (but not the string or spans)."""
copy_self = Text(