diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f7634978..5876b9a8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added options to TimeRemainingColumn to render a compact time format and render elapsed time when a task is + finished. https://github.com/Textualize/rich/pull/1992 - Added ProgressColumn `MofNCompleteColumn` to display raw `completed/total` column (similar to DownloadColumn, but displays values as ints, does not convert to floats or add bit/bytes units). https://github.com/Textualize/rich/pull/1941 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5d53b6967..de92cbd18 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -33,4 +33,4 @@ The following people have contributed to the development of Rich: - [Dennis Brakhane](https://github.com/brakhane) - [Michał Górny](https://github.com/mgorny) - [Arian Mollik Wasi](https://github.com/wasi-master) - +- [Brian Rutledge](https://github.com/bhrutledge) diff --git a/rich/progress.py b/rich/progress.py index e904b8b24..e4abbdb66 100644 --- a/rich/progress.py +++ b/rich/progress.py @@ -343,18 +343,48 @@ def render(self, task: "Task") -> Text: class TimeRemainingColumn(ProgressColumn): - """Renders estimated time remaining.""" + """Renders estimated time remaining. + + Args: + compact (bool, optional): Render MM:SS when time remaining is less than an hour. Defaults to False. + elapsed_when_finished (bool, optional): Render time elapsed when the task is finished. Defaults to False. + """ # Only refresh twice a second to prevent jitter max_refresh = 0.5 + def __init__( + self, + compact: bool = False, + elapsed_when_finished: bool = False, + table_column: Optional[Column] = None, + ): + self.compact = compact + self.elapsed_when_finished = elapsed_when_finished + super().__init__(table_column=table_column) + def render(self, task: "Task") -> Text: """Show time remaining.""" - remaining = task.time_remaining - if remaining is None: - return Text("-:--:--", style="progress.remaining") - remaining_delta = timedelta(seconds=int(remaining)) - return Text(str(remaining_delta), style="progress.remaining") + if self.elapsed_when_finished and task.finished: + task_time = task.finished_time + style = "progress.elapsed" + else: + task_time = task.time_remaining + style = "progress.remaining" + + if task_time is None: + return Text("--:--" if self.compact else "-:--:--", style=style) + + # Based on https://github.com/tqdm/tqdm/blob/master/tqdm/std.py + minutes, seconds = divmod(int(task_time), 60) + hours, minutes = divmod(minutes, 60) + + if self.compact and not hours: + formatted = f"{minutes:02d}:{seconds:02d}" + else: + formatted = f"{hours:d}:{minutes:02d}:{seconds:02d}" + + return Text(formatted, style=style) class FileSizeColumn(ProgressColumn): diff --git a/tests/test_progress.py b/tests/test_progress.py index 1941448d9..db2e825d3 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -2,6 +2,7 @@ import io from time import sleep +from types import SimpleNamespace import pytest @@ -89,6 +90,33 @@ class FakeTask(Task): assert str(text) == "0:01:00" +@pytest.mark.parametrize( + "task_time, formatted", + [ + (None, "--:--"), + (0, "00:00"), + (59, "00:59"), + (71, "01:11"), + (4210, "1:10:10"), + ], +) +def test_compact_time_remaining_column(task_time, formatted): + task = SimpleNamespace(finished=False, time_remaining=task_time) + column = TimeRemainingColumn(compact=True) + + assert str(column.render(task)) == formatted + + +def test_time_remaining_column_elapsed_when_finished(): + task_time = 71 + formatted = "0:01:11" + + task = SimpleNamespace(finished=True, finished_time=task_time) + column = TimeRemainingColumn(elapsed_when_finished=True) + + assert str(column.render(task)) == formatted + + def test_renderable_column(): column = RenderableColumn("foo") task = Task(1, "test", 100, 20, _get_time=lambda: 1.0)