httpie-cli/httpie/output/ui/rich_progress.py
Batuhan Taskaya ff6f1887b0
[Major] UI Enhancements (#1321)
* Refactor tests to use a text-based standard output. (#1318)

* Implement new style `--help` (#1316)

* Implement man page generation (#1317)

* Implement rich progress bars. (#1324)

* Man page deployment & isolation. (#1325)

* Remove all unsorted usages in the CLI docs

* Implement isolated mode for man page generation

* Add a CI job for autogenerated files

* Distribute man pages through PyPI

* Pin the date for man pages. (#1326)

* Hide suppressed arguments from --help/man pages (#1329)

* Change download spinner to line (#1328)

* Regenerate autogenerated files when pushed against to master. (#1339)

* Highlight options (#1340)

* Additional man page enhancements (#1341)

* Group options by the parent category & highlight -o/--o

* Display (and underline) the METAVAR on man pages.

* Make help message processing more robust (#1342)

* Inherit `help` from `short_help`

* Don't mirror short_help directly.

* Fixup the serialization

* Use `pager` and `man` on `--manual` when applicable (#1343)

* Run `man $program` on --manual

* Page the output of `--manual` for systems that lack man pages

* Improvements over progress bars (separate bar, status line, etc.) (#1346)

* Redesign the --help layout.

* Make our usage of rich compatible with 9.10.0

* Add `HTTPIE_NO_MAN_PAGES`

* Make tests also patch os.get_terminal_size

* Generate CLI spec from HTTPie & Man Page Hook (#1354)

* Generate CLI spec from HTTPie & add man page hook

* Use the full command space for the option headers
2022-04-14 07:43:10 -07:00

137 lines
3.8 KiB
Python

from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional
from httpie.context import Environment
if TYPE_CHECKING:
from rich.console import Console
@dataclass
class BaseDisplay:
env: Environment
def start(
self, *, total: Optional[float], at: float, description: str
) -> None:
...
def update(self, steps: float) -> None:
...
def stop(self, time_spent: float) -> None:
...
@property
def console(self) -> 'Console':
"""Returns the default console to be used with displays (stderr)."""
return self.env.rich_error_console
def _print_summary(
self,
is_finished: bool,
observed_steps: int,
time_spent: float
):
from rich import filesize
if is_finished:
verb = 'Done'
else:
verb = 'Interrupted'
total_size = filesize.decimal(observed_steps)
avg_speed = filesize.decimal(observed_steps / time_spent)
minutes, seconds = divmod(time_spent, 60)
hours, minutes = divmod(int(minutes), 60)
if hours:
total_time = f'{hours:d}:{minutes:02d}:{seconds:0.5f}'
else:
total_time = f'{minutes:02d}:{seconds:0.5f}'
self.console.print(f'[progress.description]{verb}. {total_size} in {total_time} ({avg_speed}/s)')
class DummyDisplay(BaseDisplay):
"""
A dummy display object to be used when the progress bars,
spinners etc. are disabled globally (or during tests).
"""
class StatusDisplay(BaseDisplay):
def start(
self, *, total: Optional[float], at: float, description: str
) -> None:
self.observed = at
self.description = f'[progress.description]{description}[/progress.description]'
self.status = self.console.status(self.description, spinner='line')
self.status.start()
def update(self, steps: float) -> None:
from rich import filesize
self.observed += steps
observed_amount, observed_unit = filesize.decimal(self.observed).split()
self.status.update(status=f'{self.description} [progress.download]{observed_amount}/? {observed_unit}[/progress.download]')
def stop(self, time_spent: float) -> None:
self.status.stop()
self.console.print(self.description)
if time_spent:
self._print_summary(
is_finished=True,
observed_steps=self.observed,
time_spent=time_spent
)
class ProgressDisplay(BaseDisplay):
def start(
self, *, total: Optional[float], at: float, description: str
) -> None:
from rich.progress import (
Progress,
BarColumn,
DownloadColumn,
TimeRemainingColumn,
TransferSpeedColumn,
)
assert total is not None
self.console.print(f'[progress.description]{description}')
self.progress_bar = Progress(
'[',
BarColumn(),
']',
'[progress.percentage]{task.percentage:>3.0f}%',
'(',
DownloadColumn(),
')',
TimeRemainingColumn(),
TransferSpeedColumn(),
console=self.console,
transient=True
)
self.progress_bar.start()
self.transfer_task = self.progress_bar.add_task(
description, completed=at, total=total
)
def update(self, steps: float) -> None:
self.progress_bar.advance(self.transfer_task, steps)
def stop(self, time_spent: Optional[float]) -> None:
self.progress_bar.stop()
if time_spent:
[task] = self.progress_bar.tasks
self._print_summary(
is_finished=task.finished,
observed_steps=task.completed,
time_spent=time_spent
)