From aeec6b03fe52b5daed6d3940697834427b9df6bd Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 28 Dec 2020 16:24:30 +0000 Subject: [PATCH 1/6] added time elapsed column --- CHANGELOG.md | 6 +++++ docs/source/style.rst | 2 +- pyproject.toml | 2 +- rich/color.py | 8 +++--- rich/console.py | 2 +- rich/default_styles.py | 1 + rich/markup.py | 7 +++--- rich/palette.py | 56 +++++++++++++++++++++++++++++++++++++++++- rich/progress.py | 33 +++++++++++++++++++++---- 9 files changed, 100 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad254e30..ec8b1597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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). +## [9.6.0] - Unreleased + +### Changed + +- MarkupError exception raise from None to omit internal exception + ## [9.5.1] - 2020-12-19 ### Fixed diff --git a/docs/source/style.rst b/docs/source/style.rst index 877cedd8..db8f4cf1 100644 --- a/docs/source/style.rst +++ b/docs/source/style.rst @@ -16,7 +16,7 @@ To specify a foreground color use one of the 256 :ref:`appendix-colors`. For exa console.print("Hello", style="magenta") -You may also use the color's number (an integer between 0 and 255) with the syntax `"color()"`. The following will give the equivalent output:: +You may also use the color's number (an integer between 0 and 255) with the syntax ``"color()"``. The following will give the equivalent output:: console.print("Hello", style="color(5)") diff --git a/pyproject.toml b/pyproject.toml index 500cac3a..cc75ef12 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 = "9.5.1" +version = "9.6.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" authors = ["Will McGugan "] license = "MIT" diff --git a/rich/color.py b/rich/color.py index 4ee6314e..d8a137c3 100644 --- a/rich/color.py +++ b/rich/color.py @@ -364,9 +364,9 @@ class Color(NamedTuple): """Create a truecolor from three color components in the range(0->255). Args: - red (float): Red component. - green (float): Green component. - blue (float): Blue component. + red (float): Red component in range 0-255. + green (float): Green component in range 0-255. + blue (float): Blue component in range 0-255. Returns: Color: A new color object. @@ -471,10 +471,8 @@ class Color(NamedTuple): # Convert to 8-bit color from truecolor color if system == ColorSystem.EIGHT_BIT and self.system == ColorSystem.TRUECOLOR: assert self.triplet is not None - red, green, blue = self.triplet.normalized _h, l, s = rgb_to_hls(red, green, blue) - # If saturation is under 10% assume it is grayscale if s < 0.1: gray = round(l * 25.0) diff --git a/rich/console.py b/rich/console.py index 6fa82dfd..2ef5b3d7 100644 --- a/rich/console.py +++ b/rich/console.py @@ -705,7 +705,7 @@ class Console: width: Optional[int] = None height: Optional[int] = None - if WINDOWS: # type: ignore + if WINDOWS: # pragma: no cover width, height = shutil.get_terminal_size() else: try: diff --git a/rich/default_styles.py b/rich/default_styles.py index 044f1d71..ced2ce56 100644 --- a/rich/default_styles.py +++ b/rich/default_styles.py @@ -113,6 +113,7 @@ DEFAULT_STYLES: Dict[str, Style] = { "progress.filesize": Style(color="green"), "progress.filesize.total": Style(color="green"), "progress.download": Style(color="green"), + "progress.elapsed": Style(color="yellow"), "progress.percentage": Style(color="magenta"), "progress.remaining": Style(color="cyan"), "progress.data.speed": Style(color="red"), diff --git a/rich/markup.py b/rich/markup.py index 61fb103e..a2aef136 100644 --- a/rich/markup.py +++ b/rich/markup.py @@ -104,6 +104,7 @@ def render(markup: str, style: Union[str, Style] = "", emoji: bool = True) -> Te append_span = spans.append _Span = Span + _Tag = Tag def pop_style(style_name: str) -> Tuple[int, Tag]: """Pop tag matching given style name.""" @@ -125,18 +126,18 @@ def render(markup: str, style: Union[str, Style] = "", emoji: bool = True) -> Te except KeyError: raise MarkupError( f"closing tag '{tag.markup}' at position {position} doesn't match any open tag" - ) + ) from None else: # implicit close try: start, open_tag = pop() except IndexError: raise MarkupError( f"closing tag '[/]' at position {position} has nothing to close" - ) + ) from None append_span(_Span(start, len(text), str(open_tag))) else: # Opening tag - normalized_tag = Tag(normalize(tag.name), tag.parameters) + normalized_tag = _Tag(normalize(tag.name), tag.parameters) style_stack.append((len(text), normalized_tag)) text_length = len(text) diff --git a/rich/palette.py b/rich/palette.py index 3faa23fa..80dff5ed 100644 --- a/rich/palette.py +++ b/rich/palette.py @@ -1,9 +1,12 @@ from math import sqrt from functools import lru_cache -from typing import Sequence, Tuple +from typing import Sequence, Tuple, TYPE_CHECKING from .color_triplet import ColorTriplet +if TYPE_CHECKING: + from rich.table import Table + class Palette: """A palette of available colors.""" @@ -14,6 +17,29 @@ class Palette: def __getitem__(self, number: int) -> ColorTriplet: return ColorTriplet(*self._colors[number]) + def __rich__(self) -> "Table": + from rich.color import Color + from rich.style import Style + from rich.text import Text + from rich.table import Table + + table = Table( + "index", + "RGB", + "Color", + title="Palette", + caption=f"{len(self._colors)} colors", + highlight=True, + caption_justify="right", + ) + for index, color in enumerate(self._colors): + table.add_row( + str(index), + repr(color), + Text(" " * 16, style=Style(bgcolor=Color.from_rgb(*color))), + ) + return table + # This is somewhat inefficient and needs caching @lru_cache(maxsize=1024) def match(self, color: Tuple[int, int, int]) -> int: @@ -43,3 +69,31 @@ class Palette: min_index = min(range(len(self._colors)), key=get_color_distance) return min_index + + +if __name__ == "__main__": # pragma: no cover + import colorsys + from typing import Iterable + from rich.color import Color + from rich.console import Console, ConsoleOptions + from rich.segment import Segment + from rich.style import Style + + class ColorBox: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> Iterable[Segment]: + height = console.size.height - 3 + for y in range(0, height): + for x in range(options.max_width): + h = x / options.max_width + l = y / (height + 1) + r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0) + r2, g2, b2 = colorsys.hls_to_rgb(h, l + (1 / height / 2), 1.0) + bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255) + color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255) + yield Segment("▄", Style(color=color, bgcolor=bgcolor)) + yield Segment.line() + + console = Console() + console.print(ColorBox()) diff --git a/rich/progress.py b/rich/progress.py index b38e67ae..f9895e5d 100644 --- a/rich/progress.py +++ b/rich/progress.py @@ -322,6 +322,18 @@ class BarColumn(ProgressColumn): ) +class TimeElapsedColumn(ProgressColumn): + """Renders time elapsed.""" + + def render(self, task: "Task") -> Text: + """Show time remaining.""" + elapsed = task.finished_time if task.finished else task.elapsed + if elapsed is None: + return Text("-:--:--", style="progress.elapsed") + delta = timedelta(seconds=int(elapsed)) + return Text(str(delta), style="progress.elapsed") + + class TimeRemainingColumn(ProgressColumn): """Renders estimated time remaining.""" @@ -434,6 +446,9 @@ class Task: _get_time: GetTimeCallable """Callable to get the current time.""" + finished_time: Optional[float] = None + """float: Time task was finished.""" + visible: bool = True """bool: Indicates if this task is visible in the progress display.""" @@ -475,8 +490,8 @@ class Task: @property def finished(self) -> bool: - """bool: Check if the task has completed.""" - return self.completed >= self.total + """Check if the task has finished.""" + return self.finished_time is not None @property def percentage(self) -> float: @@ -518,6 +533,7 @@ class Task: def _reset(self) -> None: """Reset progress.""" self._progress.clear() + self.finished_time = None class _RefreshThread(Thread): @@ -799,9 +815,6 @@ class Progress(JupyterMixin, RenderHook): task.fields.update(fields) update_completed = task.completed - completed_start - if refresh: - self.refresh() - current_time = self.get_time() old_sample_time = current_time - self.speed_estimate_period _progress = task._progress @@ -813,6 +826,11 @@ class Progress(JupyterMixin, RenderHook): popleft() if update_completed > 0: _progress.append(ProgressSample(current_time, update_completed)) + if task.completed >= task.total and task.finished_time is None: + task.finished_time = task.elapsed + + if refresh: + self.refresh() def reset( self, @@ -848,6 +866,7 @@ class Progress(JupyterMixin, RenderHook): task.fields = fields if description is not None: task.description = description + task.finished_time = None self.refresh() def advance(self, task_id: TaskID, advance: float = 1) -> None: @@ -872,6 +891,8 @@ class Progress(JupyterMixin, RenderHook): while len(_progress) > 1000: popleft() _progress.append(ProgressSample(current_time, update_completed)) + if task.completed >= task.total and task.finished_time is None: + task.finished_time = task.elapsed def refresh(self) -> None: """Refresh (render) the progress information.""" @@ -1057,6 +1078,7 @@ if __name__ == "__main__": # pragma: no coverage BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), TimeRemainingColumn(), + TimeElapsedColumn(), console=console, transient=True, ) as progress: @@ -1074,3 +1096,4 @@ if __name__ == "__main__": # pragma: no coverage except: console.save_html("progress.html") print("wrote progress.html") + raise From 6d616467f4e90427ec6e93b9030ea87ba2c11e3a Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 28 Dec 2020 16:33:46 +0000 Subject: [PATCH 2/6] test fix --- rich/progress.py | 6 +++--- tests/test_progress.py | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/rich/progress.py b/rich/progress.py index f9895e5d..56e5541e 100644 --- a/rich/progress.py +++ b/rich/progress.py @@ -815,6 +815,9 @@ class Progress(JupyterMixin, RenderHook): task.fields.update(fields) update_completed = task.completed - completed_start + if refresh: + self.refresh() + current_time = self.get_time() old_sample_time = current_time - self.speed_estimate_period _progress = task._progress @@ -829,9 +832,6 @@ class Progress(JupyterMixin, RenderHook): if task.completed >= task.total and task.finished_time is None: task.finished_time = task.elapsed - if refresh: - self.refresh() - def reset( self, task_id: TaskID, diff --git a/tests/test_progress.py b/tests/test_progress.py index 7d65b5f3..8f1ecb53 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -17,6 +17,7 @@ from rich.progress import ( Progress, Task, TextColumn, + TimeElapsedColumn, TimeRemainingColumn, track, _TrackThread, @@ -262,6 +263,7 @@ def test_columns() -> None: TextColumn("{task.description}"), BarColumn(bar_width=None), TimeRemainingColumn(), + TimeElapsedColumn(), FileSizeColumn(), TotalFileSizeColumn(), DownloadColumn(), @@ -285,7 +287,7 @@ def test_columns() -> None: result = replace_link_ids(console.file.getvalue()) print(repr(result)) - expected = "\x1b[?25ltest foo \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m0/10 bytes\x1b[0m \x1b[31m?\x1b[0m\ntest bar \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m0/7 bytes \x1b[0m \x1b[31m?\x1b[0m\r\x1b[2K\x1b[1A\x1b[2Kfoo\ntest foo \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m0/10 bytes\x1b[0m \x1b[31m?\x1b[0m\ntest bar \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m0/7 bytes \x1b[0m \x1b[31m?\x1b[0m\r\x1b[2K\x1b[1A\x1b[2K\x1b[2;36m[TIME]\x1b[0m\x1b[2;36m \x1b[0mhello \ntest foo \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m0/10 bytes\x1b[0m \x1b[31m?\x1b[0m\ntest bar \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m0/7 bytes \x1b[0m \x1b[31m?\x1b[0m\r\x1b[2K\x1b[1A\x1b[2Kworld\ntest foo \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m0/10 bytes\x1b[0m \x1b[31m?\x1b[0m\ntest bar \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m0/7 bytes \x1b[0m \x1b[31m?\x1b[0m\r\x1b[2K\x1b[1A\x1b[2Ktest foo \x1b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m0:00:00\x1b[0m \x1b[32m12 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m12/10 bytes\x1b[0m \x1b[31m1 byte/s \x1b[0m\ntest bar \x1b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m0:00:00\x1b[0m \x1b[32m16 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m16/7 bytes \x1b[0m \x1b[31m2 bytes/s\x1b[0m\r\x1b[2K\x1b[1A\x1b[2Ktest foo \x1b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m0:00:00\x1b[0m \x1b[32m12 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m12/10 bytes\x1b[0m \x1b[31m1 byte/s \x1b[0m\ntest bar \x1b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m0:00:00\x1b[0m \x1b[32m16 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m16/7 bytes \x1b[0m \x1b[31m2 bytes/s\x1b[0m\n\x1b[?25h\r\x1b[1A\x1b[2K\x1b[1A\x1b[2K" + expected = "\x1b[?25ltest foo \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[33m0:00:37\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m0/10 bytes\x1b[0m \x1b[31m?\x1b[0m\ntest bar \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[33m0:00:36\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m0/7 bytes \x1b[0m \x1b[31m?\x1b[0m\r\x1b[2K\x1b[1A\x1b[2Kfoo\ntest foo \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[33m0:00:37\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m0/10 bytes\x1b[0m \x1b[31m?\x1b[0m\ntest bar \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[33m0:00:36\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m0/7 bytes \x1b[0m \x1b[31m?\x1b[0m\r\x1b[2K\x1b[1A\x1b[2K\x1b[2;36m[TIME]\x1b[0m\x1b[2;36m \x1b[0mhello \ntest foo \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[33m0:00:37\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m0/10 bytes\x1b[0m \x1b[31m?\x1b[0m\ntest bar \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[33m0:00:36\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m0/7 bytes \x1b[0m \x1b[31m?\x1b[0m\r\x1b[2K\x1b[1A\x1b[2Kworld\ntest foo \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[33m0:00:37\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m0/10 bytes\x1b[0m \x1b[31m?\x1b[0m\ntest bar \x1b[38;5;237m━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m-:--:--\x1b[0m \x1b[33m0:00:36\x1b[0m \x1b[32m0 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m0/7 bytes \x1b[0m \x1b[31m?\x1b[0m\r\x1b[2K\x1b[1A\x1b[2Ktest foo \x1b[38;2;114;156;31m━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m0:00:00\x1b[0m \x1b[33m0:01:00\x1b[0m \x1b[32m12 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m12/10 bytes\x1b[0m \x1b[31m1 byte/s\x1b[0m\ntest bar \x1b[38;2;114;156;31m━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m0:00:00\x1b[0m \x1b[33m0:00:45\x1b[0m \x1b[32m16 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m16/7 bytes \x1b[0m \x1b[31m1 byte/s\x1b[0m\r\x1b[2K\x1b[1A\x1b[2Ktest foo \x1b[38;2;114;156;31m━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m0:00:00\x1b[0m \x1b[33m0:01:00\x1b[0m \x1b[32m12 bytes\x1b[0m \x1b[32m10 bytes\x1b[0m \x1b[32m12/10 bytes\x1b[0m \x1b[31m1 byte/s\x1b[0m\ntest bar \x1b[38;2;114;156;31m━━━━━━━━━━━━━━━━\x1b[0m \x1b[36m0:00:00\x1b[0m \x1b[33m0:00:45\x1b[0m \x1b[32m16 bytes\x1b[0m \x1b[32m7 bytes \x1b[0m \x1b[32m16/7 bytes \x1b[0m \x1b[31m1 byte/s\x1b[0m\n\x1b[?25h\r\x1b[1A\x1b[2K\x1b[1A\x1b[2K" assert result == expected From 8033eade3cc47759344cd15ef0bbbcc753203664 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 28 Dec 2020 16:34:43 +0000 Subject: [PATCH 3/6] test palette --- tests/test_palette.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/test_palette.py diff --git a/tests/test_palette.py b/tests/test_palette.py new file mode 100644 index 00000000..7a11f4ee --- /dev/null +++ b/tests/test_palette.py @@ -0,0 +1,8 @@ +from rich._palettes import STANDARD_PALETTE +from rich.table import Table + + +def test_rich_cast(): + table = STANDARD_PALETTE.__rich__() + assert isinstance(table, Table) + assert table.row_count == 16 \ No newline at end of file From 696e85da0719b21dcb8ef282970c5e44042230ee Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 28 Dec 2020 16:35:55 +0000 Subject: [PATCH 4/6] black --- tests/test_palette.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_palette.py b/tests/test_palette.py index 7a11f4ee..8ddd7cc9 100644 --- a/tests/test_palette.py +++ b/tests/test_palette.py @@ -5,4 +5,4 @@ from rich.table import Table def test_rich_cast(): table = STANDARD_PALETTE.__rich__() assert isinstance(table, Table) - assert table.row_count == 16 \ No newline at end of file + assert table.row_count == 16 From a0539c05085a8f76cc9c509c8f7c101fa1fa5695 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 28 Dec 2020 16:39:08 +0000 Subject: [PATCH 5/6] typecheck issue --- rich/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rich/color.py b/rich/color.py index d8a137c3..59dc9f88 100644 --- a/rich/color.py +++ b/rich/color.py @@ -564,7 +564,7 @@ if __name__ == "__main__": # pragma: no cover if color_number < 16: table.add_row(color_cell, f"{color_number}", Text(f'"{name}"')) else: - color = EIGHT_BIT_PALETTE[color_number] + color = EIGHT_BIT_PALETTE[color_number] # type: ignore table.add_row( color_cell, str(color_number), Text(f'"{name}"'), color.hex, color.rgb ) From 389a7d0176f2c45f84503f80e5e8a4728ceebf24 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 28 Dec 2020 16:41:34 +0000 Subject: [PATCH 6/6] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec8b1597..7a195f11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - MarkupError exception raise from None to omit internal exception +### Added + +- Added Progress.TimeElapsedColumn + ## [9.5.1] - 2020-12-19 ### Fixed