Skip to content

Commit 8e402e4

Browse files
committed
clear method, optimizations, export links
1 parent 51387ef commit 8e402e4

9 files changed

+105
-47
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.1.8] - 2020-05-20
9+
10+
### Changed
11+
12+
- Optimizations for Segment, Console and Table
13+
14+
### Added
15+
16+
- Added Console.clear method
17+
- Added exporting of links to HTML
18+
819
## [1.1.7] - 2020-05-19
920

1021
### Added

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "rich"
33
homepage = "https://github.com/willmcgugan/rich"
44
documentation = "https://rich.readthedocs.io/en/latest/"
5-
version = "1.1.7"
5+
version = "1.1.8"
66
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
77
authors = ["Will McGugan <willmcgugan@gmail.com>"]
88
license = "MIT"

rich/__main__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ def make_test_card() -> Table:
6363
)
6464

6565
lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque in metus sed sapien ultricies pretium a at justo. Maecenas luctus velit et auctor maximus. Donec faucibus vel arcu id pretium."
66-
lorem_table = Table.grid()
67-
lorem_table.padding = (0, 1, 0, 1)
66+
lorem_table = Table.grid(padding=1, collapse_padding=True)
6867
lorem_table.pad_edge = False
6968
lorem_table.add_row(
7069
Text(lorem, justify="left", style="green"),
@@ -83,7 +82,7 @@ def make_test_card() -> Table:
8382
)
8483

8584
def comparison(renderable1, renderable2) -> Table:
86-
table = Table(show_header=False, box=None, expand=True)
85+
table = Table(show_header=False, pad_edge=False, box=None, expand=True)
8786
table.add_column("1", ratio=1)
8887
table.add_column("2", ratio=1)
8988
table.add_row(renderable1, renderable2)

rich/console.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,16 @@ def line(self, count: int = 1) -> None:
425425
self._buffer.append(Segment("\n" * count))
426426
self._check_buffer()
427427

428+
def clear(self, home: bool = True) -> None:
429+
"""Clear the screen.
430+
431+
Args:
432+
home (bool, optional): Also move the cursor to 'home' position. Defaults to True.
433+
"""
434+
if self.is_terminal:
435+
self._check_buffer()
436+
self.file.write("\033[2J\033[H" if home else "\033[2J")
437+
428438
def show_cursor(self, show: bool = True) -> None:
429439
"""Show or hide the cursor.
430440
@@ -436,7 +446,7 @@ def show_cursor(self, show: bool = True) -> None:
436446
self.file.write("\033[?25h" if show else "\033[?25l")
437447

438448
def _render(
439-
self, renderable: RenderableType, options: Optional[ConsoleOptions],
449+
self, renderable: RenderableType, options: ConsoleOptions,
440450
) -> Iterable[Segment]:
441451
"""Render an object in to an iterable of `Segment` instances.
442452
@@ -446,17 +456,16 @@ def _render(
446456
Args:
447457
renderable (RenderableType): An object supporting the console protocol, or
448458
an object that may be converted to a string.
449-
options (ConsoleOptions, optional): An options objects. Defaults to None.
459+
options (ConsoleOptions): An options objects. Defaults to None.
450460
451461
Returns:
452462
Iterable[Segment]: An iterable of segments that may be rendered.
453463
"""
454464
render_iterable: RenderResult
455-
render_options = options or self.options
456465
if isinstance(renderable, ConsoleRenderable):
457-
render_iterable = renderable.__console__(self, render_options)
466+
render_iterable = renderable.__console__(self, options)
458467
elif isinstance(renderable, str):
459-
yield from self.render(self.render_str(renderable), render_options)
468+
yield from self._render(self.render_str(renderable), options)
460469
return
461470
else:
462471
raise errors.NotRenderableError(
@@ -474,7 +483,7 @@ def _render(
474483
if isinstance(render_output, Segment):
475484
yield render_output
476485
else:
477-
yield from self.render(render_output, render_options)
486+
yield from self._render(render_output, options)
478487

479488
def render(
480489
self, renderable: RenderableType, options: Optional[ConsoleOptions] = None
@@ -969,9 +978,10 @@ def escape(text: str) -> str:
969978
text = escape(text)
970979
if style:
971980
rule = style.get_html_style(_theme)
972-
append(f'<span style="{rule}">{text}</span>' if rule else text)
973-
else:
974-
append(text)
981+
text = f'<span style="{rule}">{text}</span>' if rule else text
982+
if style.link:
983+
text = f'<a href="{style.link}">{text}</a>'
984+
append(text)
975985
else:
976986
styles: Dict[str, int] = {}
977987
for text, style, _ in Segment.filter_control(
@@ -982,11 +992,10 @@ def escape(text: str) -> str:
982992
rule = style.get_html_style(_theme)
983993
if rule:
984994
style_number = styles.setdefault(rule, len(styles) + 1)
985-
append(f'<span class="r{style_number}">{text}</span>')
986-
else:
987-
append(text)
988-
else:
989-
append(text)
995+
text = f'<span class="r{style_number}">{text}</span>'
996+
if style.link:
997+
text = f'<a href="{style.link}">{text}</a>'
998+
append(text)
990999
stylesheet_rules: List[str] = []
9911000
stylesheet_append = stylesheet_rules.append
9921001
for style_rule, style_number in styles.items():

rich/segment.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from .cells import cell_len, set_cell_size
44
from .style import Style, StyleType
55

6-
from itertools import zip_longest
6+
from itertools import filterfalse, zip_longest
7+
from operator import attrgetter
78
from typing import Iterable, List, Tuple
89

910

@@ -81,7 +82,10 @@ def filter_control(
8182
Iterable[Segment]: And iterable of Segment instances.
8283
8384
"""
84-
return (segment for segment in segments if segment.is_control == is_control)
85+
if is_control:
86+
return filter(attrgetter("is_control"), segments)
87+
else:
88+
return filterfalse(attrgetter("is_control"), segments)
8589

8690
@classmethod
8791
def split_lines(cls, segments: Iterable["Segment"]) -> Iterable[List["Segment"]]:
@@ -104,8 +108,9 @@ def split_lines(cls, segments: Iterable["Segment"]) -> Iterable[List["Segment"]]
104108
if _text:
105109
append(cls(_text, style))
106110
if new_line:
107-
yield line[:]
108-
del line[:]
111+
yield line
112+
line = []
113+
append = line.append
109114
else:
110115
append(segment)
111116
if line:
@@ -132,8 +137,11 @@ def split_and_crop_lines(
132137
Returns:
133138
Iterable[List[Segment]]: An iterable of lines of segments.
134139
"""
135-
lines: List[List[Segment]] = [[]]
136-
append = lines[-1].append
140+
line: List[Segment] = []
141+
append = line.append
142+
143+
adjust_line_length = cls.adjust_line_length
144+
new_line_segment = cls("\n")
137145

138146
for segment in segments:
139147
if "\n" in segment.text and not segment.is_control:
@@ -143,18 +151,17 @@ def split_and_crop_lines(
143151
if _text:
144152
append(cls(_text, style))
145153
if new_line:
146-
line = cls.adjust_line_length(
147-
lines[-1], length, style=style, pad=pad,
154+
cropped_line = adjust_line_length(
155+
line, length, style=style, pad=pad
148156
)
149157
if include_new_lines:
150-
line.append(cls("\n"))
151-
yield line
152-
lines.append([])
153-
append = lines[-1].append
158+
cropped_line.append(new_line_segment)
159+
yield cropped_line
160+
del line[:]
154161
else:
155162
append(segment)
156-
if lines[-1]:
157-
yield cls.adjust_line_length(lines[-1], length, style=style, pad=pad)
163+
if line:
164+
yield adjust_line_length(line, length, style=style, pad=pad)
158165

159166
@classmethod
160167
def adjust_line_length(

rich/table.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -490,11 +490,30 @@ def _render(
490490
show_edge = self.show_edge
491491
show_lines = self.show_lines
492492

493+
_Segment = Segment
493494
if _box and show_edge:
495+
box_segments = [
496+
(
497+
_Segment(_box.head_left, border_style),
498+
_Segment(_box.head_right, border_style),
499+
_Segment(_box.head_vertical, border_style),
500+
),
501+
(
502+
Segment(_box.foot_left, border_style),
503+
_Segment(_box.foot_right, border_style),
504+
_Segment(_box.foot_vertical, border_style),
505+
),
506+
(
507+
_Segment(_box.mid_left, border_style),
508+
_Segment(_box.mid_right, border_style),
509+
_Segment(_box.mid_vertical, border_style),
510+
),
511+
]
494512
yield Segment(_box.get_top(widths), border_style)
495513
yield new_line
514+
else:
515+
box_segments = []
496516

497-
_Segment = Segment
498517
get_row_style = self.get_row_style
499518
get_style = console.get_style
500519
for index, (first, last, row) in enumerate(loop_first_last(rows)):
@@ -529,17 +548,11 @@ def _render(
529548
)
530549
yield new_line
531550
if first:
532-
left = _Segment(_box.head_left, border_style)
533-
right = _Segment(_box.head_right, border_style)
534-
divider = _Segment(_box.head_vertical, border_style)
551+
left, right, divider = box_segments[0]
535552
elif last:
536-
left = _Segment(_box.foot_left, border_style)
537-
right = _Segment(_box.foot_right, border_style)
538-
divider = _Segment(_box.foot_vertical, border_style)
553+
left, right, divider = box_segments[2]
539554
else:
540-
left = _Segment(_box.mid_left, border_style)
541-
right = _Segment(_box.mid_right, border_style)
542-
divider = _Segment(_box.mid_vertical, border_style)
555+
left, right, divider = box_segments[1]
543556

544557
for line_no in range(max_height):
545558
if show_edge:

tests/_card_render.py

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/test_console.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ def test_show_cursor():
9090
assert console.file.getvalue() == "\x1b[?25lfoo\n\x1b[?25h"
9191

9292

93+
def test_clear():
94+
console = Console(file=io.StringIO(), force_terminal=True)
95+
console.clear()
96+
console.clear(home=False)
97+
assert console.file.getvalue() == "\033[2J\033[H" + "\033[2J"
98+
99+
100+
def test_clear_no_terminal():
101+
console = Console(file=io.StringIO())
102+
console.clear()
103+
console.clear(home=False)
104+
assert console.file.getvalue() == ""
105+
106+
93107
def test_get_style():
94108
console = Console()
95109
console.get_style("repr.brace") == Style(bold=True)
@@ -197,17 +211,17 @@ def test_export_text():
197211

198212
def test_export_html():
199213
console = Console(record=True, width=100)
200-
console.print("[b]foo")
214+
console.print("[b]foo [link=https://example.org]Click[/link]")
201215
html = console.export_html()
202-
expected = "<!DOCTYPE html>\n<head>\n<style>\n.r1 {font-weight: bold}\nbody {\n color: #000000;\n background-color: #ffffff;\n}\n</style>\n</head>\n<html>\n<body>\n <code>\n <pre style=\"font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span class=\"r1\">foo</span>\n</pre>\n </code>\n</body>\n</html>\n"
216+
expected = '<!DOCTYPE html>\n<head>\n<style>\n.r1 {font-weight: bold}\nbody {\n color: #000000;\n background-color: #ffffff;\n}\n</style>\n</head>\n<html>\n<body>\n <code>\n <pre style="font-family:Menlo,\'DejaVu Sans Mono\',consolas,\'Courier New\',monospace"><span class="r1">foo </span><a href="https://example.org"><span class="r1">Click</span></a>\n</pre>\n </code>\n</body>\n</html>\n'
203217
assert html == expected
204218

205219

206220
def test_export_html_inline():
207221
console = Console(record=True, width=100)
208-
console.print("[b]foo")
222+
console.print("[b]foo [link=https://example.org]Click[/link]")
209223
html = console.export_html(inline_styles=True)
210-
expected = "<!DOCTYPE html>\n<head>\n<style>\n\nbody {\n color: #000000;\n background-color: #ffffff;\n}\n</style>\n</head>\n<html>\n<body>\n <code>\n <pre style=\"font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\">foo</span>\n</pre>\n </code>\n</body>\n</html>\n"
224+
expected = '<!DOCTYPE html>\n<head>\n<style>\n\nbody {\n color: #000000;\n background-color: #ffffff;\n}\n</style>\n</head>\n<html>\n<body>\n <code>\n <pre style="font-family:Menlo,\'DejaVu Sans Mono\',consolas,\'Courier New\',monospace"><span style="font-weight: bold">foo </span><a href="https://example.org"><span style="font-weight: bold">Click</span></a>\n</pre>\n </code>\n</body>\n</html>\n'
211225
assert html == expected
212226

213227

tests/test_segment.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ def test_apply_style():
1919
]
2020

2121

22+
def test_split_lines():
23+
lines = [Segment("Hello\nWorld")]
24+
assert list(Segment.split_lines(lines)) == [[Segment("Hello")], [Segment("World")]]
25+
26+
2227
def test_split_and_crop_lines():
2328
assert list(
2429
Segment.split_and_crop_lines([Segment("Hello\nWorld!\n"), Segment("foo")], 4)

0 commit comments

Comments
 (0)