Uploaded Test files
This commit is contained in:
		
							parent
							
								
									f584ad9d97
								
							
						
					
					
						commit
						2e81cb7d99
					
				
					 16627 changed files with 2065359 additions and 102444 deletions
				
			
		|  | @ -0,0 +1,31 @@ | |||
| from .base import ProgressBar, ProgressBarCounter | ||||
| from .formatters import ( | ||||
|     Bar, | ||||
|     Formatter, | ||||
|     IterationsPerSecond, | ||||
|     Label, | ||||
|     Percentage, | ||||
|     Progress, | ||||
|     Rainbow, | ||||
|     SpinningWheel, | ||||
|     Text, | ||||
|     TimeElapsed, | ||||
|     TimeLeft, | ||||
| ) | ||||
| 
 | ||||
| __all__ = [ | ||||
|     "ProgressBar", | ||||
|     "ProgressBarCounter", | ||||
|     # Formatters. | ||||
|     "Formatter", | ||||
|     "Text", | ||||
|     "Label", | ||||
|     "Percentage", | ||||
|     "Bar", | ||||
|     "Progress", | ||||
|     "TimeElapsed", | ||||
|     "TimeLeft", | ||||
|     "IterationsPerSecond", | ||||
|     "SpinningWheel", | ||||
|     "Rainbow", | ||||
| ] | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -0,0 +1,451 @@ | |||
| """ | ||||
| Progress bar implementation on top of prompt_toolkit. | ||||
| 
 | ||||
| :: | ||||
| 
 | ||||
|     with ProgressBar(...) as pb: | ||||
|         for item in pb(data): | ||||
|             ... | ||||
| """ | ||||
| import datetime | ||||
| import functools | ||||
| import os | ||||
| import signal | ||||
| import threading | ||||
| import traceback | ||||
| from asyncio import get_event_loop, new_event_loop, set_event_loop | ||||
| from typing import ( | ||||
|     TYPE_CHECKING, | ||||
|     Generic, | ||||
|     Iterable, | ||||
|     List, | ||||
|     Optional, | ||||
|     Sequence, | ||||
|     Sized, | ||||
|     TextIO, | ||||
|     TypeVar, | ||||
|     cast, | ||||
| ) | ||||
| 
 | ||||
| from prompt_toolkit.application import Application | ||||
| from prompt_toolkit.application.current import get_app_session | ||||
| from prompt_toolkit.filters import Condition, is_done, renderer_height_is_known | ||||
| from prompt_toolkit.formatted_text import ( | ||||
|     AnyFormattedText, | ||||
|     StyleAndTextTuples, | ||||
|     to_formatted_text, | ||||
| ) | ||||
| from prompt_toolkit.input import Input | ||||
| from prompt_toolkit.key_binding import KeyBindings | ||||
| from prompt_toolkit.key_binding.key_processor import KeyPressEvent | ||||
| from prompt_toolkit.layout import ( | ||||
|     ConditionalContainer, | ||||
|     FormattedTextControl, | ||||
|     HSplit, | ||||
|     Layout, | ||||
|     VSplit, | ||||
|     Window, | ||||
| ) | ||||
| from prompt_toolkit.layout.controls import UIContent, UIControl | ||||
| from prompt_toolkit.layout.dimension import AnyDimension, D | ||||
| from prompt_toolkit.output import ColorDepth, Output | ||||
| from prompt_toolkit.styles import BaseStyle | ||||
| from prompt_toolkit.utils import in_main_thread | ||||
| 
 | ||||
| from .formatters import Formatter, create_default_formatters | ||||
| 
 | ||||
| try: | ||||
|     import contextvars | ||||
| except ImportError: | ||||
|     from prompt_toolkit.eventloop import dummy_contextvars | ||||
| 
 | ||||
|     contextvars = dummy_contextvars  # type: ignore | ||||
| 
 | ||||
| 
 | ||||
| __all__ = ["ProgressBar"] | ||||
| 
 | ||||
| E = KeyPressEvent | ||||
| 
 | ||||
| _SIGWINCH = getattr(signal, "SIGWINCH", None) | ||||
| 
 | ||||
| 
 | ||||
| def create_key_bindings() -> KeyBindings: | ||||
|     """ | ||||
|     Key bindings handled by the progress bar. | ||||
|     (The main thread is not supposed to handle any key bindings.) | ||||
|     """ | ||||
|     kb = KeyBindings() | ||||
| 
 | ||||
|     @kb.add("c-l") | ||||
|     def _clear(event: E) -> None: | ||||
|         event.app.renderer.clear() | ||||
| 
 | ||||
|     @kb.add("c-c") | ||||
|     def _interrupt(event: E) -> None: | ||||
|         # Send KeyboardInterrupt to the main thread. | ||||
|         os.kill(os.getpid(), signal.SIGINT) | ||||
| 
 | ||||
|     return kb | ||||
| 
 | ||||
| 
 | ||||
| _T = TypeVar("_T") | ||||
| 
 | ||||
| 
 | ||||
| class ProgressBar: | ||||
|     """ | ||||
|     Progress bar context manager. | ||||
| 
 | ||||
|     Usage :: | ||||
| 
 | ||||
|         with ProgressBar(...) as pb: | ||||
|             for item in pb(data): | ||||
|                 ... | ||||
| 
 | ||||
|     :param title: Text to be displayed above the progress bars. This can be a | ||||
|         callable or formatted text as well. | ||||
|     :param formatters: List of :class:`.Formatter` instances. | ||||
|     :param bottom_toolbar: Text to be displayed in the bottom toolbar. This | ||||
|         can be a callable or formatted text. | ||||
|     :param style: :class:`prompt_toolkit.styles.BaseStyle` instance. | ||||
|     :param key_bindings: :class:`.KeyBindings` instance. | ||||
|     :param file: The file object used for rendering, by default `sys.stderr` is used. | ||||
| 
 | ||||
|     :param color_depth: `prompt_toolkit` `ColorDepth` instance. | ||||
|     :param output: :class:`~prompt_toolkit.output.Output` instance. | ||||
|     :param input: :class:`~prompt_toolkit.input.Input` instance. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, | ||||
|         title: AnyFormattedText = None, | ||||
|         formatters: Optional[Sequence[Formatter]] = None, | ||||
|         bottom_toolbar: AnyFormattedText = None, | ||||
|         style: Optional[BaseStyle] = None, | ||||
|         key_bindings: Optional[KeyBindings] = None, | ||||
|         file: Optional[TextIO] = None, | ||||
|         color_depth: Optional[ColorDepth] = None, | ||||
|         output: Optional[Output] = None, | ||||
|         input: Optional[Input] = None, | ||||
|     ) -> None: | ||||
| 
 | ||||
|         self.title = title | ||||
|         self.formatters = formatters or create_default_formatters() | ||||
|         self.bottom_toolbar = bottom_toolbar | ||||
|         self.counters: List[ProgressBarCounter[object]] = [] | ||||
|         self.style = style | ||||
|         self.key_bindings = key_bindings | ||||
| 
 | ||||
|         # Note that we use __stderr__ as default error output, because that | ||||
|         # works best with `patch_stdout`. | ||||
|         self.color_depth = color_depth | ||||
|         self.output = output or get_app_session().output | ||||
|         self.input = input or get_app_session().input | ||||
| 
 | ||||
|         self._thread: Optional[threading.Thread] = None | ||||
| 
 | ||||
|         self._loop = get_event_loop() | ||||
|         self._app_loop = new_event_loop() | ||||
| 
 | ||||
|         self._previous_winch_handler = None | ||||
|         self._has_sigwinch = False | ||||
| 
 | ||||
|         if TYPE_CHECKING: | ||||
|             # Infer type from getsignal result, as defined in typeshed. Too | ||||
|             # complex to repeat here. | ||||
|             self._previous_winch_handler = signal.getsignal(_SIGWINCH) | ||||
| 
 | ||||
|     def __enter__(self) -> "ProgressBar": | ||||
|         # Create UI Application. | ||||
|         title_toolbar = ConditionalContainer( | ||||
|             Window( | ||||
|                 FormattedTextControl(lambda: self.title), | ||||
|                 height=1, | ||||
|                 style="class:progressbar,title", | ||||
|             ), | ||||
|             filter=Condition(lambda: self.title is not None), | ||||
|         ) | ||||
| 
 | ||||
|         bottom_toolbar = ConditionalContainer( | ||||
|             Window( | ||||
|                 FormattedTextControl( | ||||
|                     lambda: self.bottom_toolbar, style="class:bottom-toolbar.text" | ||||
|                 ), | ||||
|                 style="class:bottom-toolbar", | ||||
|                 height=1, | ||||
|             ), | ||||
|             filter=~is_done | ||||
|             & renderer_height_is_known | ||||
|             & Condition(lambda: self.bottom_toolbar is not None), | ||||
|         ) | ||||
| 
 | ||||
|         def width_for_formatter(formatter: Formatter) -> AnyDimension: | ||||
|             # Needs to be passed as callable (partial) to the 'width' | ||||
|             # parameter, because we want to call it on every resize. | ||||
|             return formatter.get_width(progress_bar=self) | ||||
| 
 | ||||
|         progress_controls = [ | ||||
|             Window( | ||||
|                 content=_ProgressControl(self, f), | ||||
|                 width=functools.partial(width_for_formatter, f), | ||||
|             ) | ||||
|             for f in self.formatters | ||||
|         ] | ||||
| 
 | ||||
|         self.app: Application[None] = Application( | ||||
|             min_redraw_interval=0.05, | ||||
|             layout=Layout( | ||||
|                 HSplit( | ||||
|                     [ | ||||
|                         title_toolbar, | ||||
|                         VSplit( | ||||
|                             progress_controls, | ||||
|                             height=lambda: D( | ||||
|                                 preferred=len(self.counters), max=len(self.counters) | ||||
|                             ), | ||||
|                         ), | ||||
|                         Window(), | ||||
|                         bottom_toolbar, | ||||
|                     ] | ||||
|                 ) | ||||
|             ), | ||||
|             style=self.style, | ||||
|             key_bindings=self.key_bindings, | ||||
|             refresh_interval=0.3, | ||||
|             color_depth=self.color_depth, | ||||
|             output=self.output, | ||||
|             input=self.input, | ||||
|         ) | ||||
| 
 | ||||
|         # Run application in different thread. | ||||
|         def run() -> None: | ||||
|             set_event_loop(self._app_loop) | ||||
|             try: | ||||
|                 self.app.run() | ||||
|             except BaseException as e: | ||||
|                 traceback.print_exc() | ||||
|                 print(e) | ||||
| 
 | ||||
|         ctx: contextvars.Context = contextvars.copy_context() | ||||
| 
 | ||||
|         self._thread = threading.Thread(target=ctx.run, args=(run,)) | ||||
|         self._thread.start() | ||||
| 
 | ||||
|         # Attach WINCH signal handler in main thread. | ||||
|         # (Interrupt that we receive during resize events.) | ||||
|         self._has_sigwinch = _SIGWINCH is not None and in_main_thread() | ||||
|         if self._has_sigwinch: | ||||
|             self._previous_winch_handler = signal.getsignal(_SIGWINCH) | ||||
|             self._loop.add_signal_handler(_SIGWINCH, self.invalidate) | ||||
| 
 | ||||
|         return self | ||||
| 
 | ||||
|     def __exit__(self, *a: object) -> None: | ||||
|         # Quit UI application. | ||||
|         if self.app.is_running: | ||||
|             self.app.exit() | ||||
| 
 | ||||
|         # Remove WINCH handler. | ||||
|         if self._has_sigwinch: | ||||
|             self._loop.remove_signal_handler(_SIGWINCH) | ||||
|             signal.signal(_SIGWINCH, self._previous_winch_handler) | ||||
| 
 | ||||
|         if self._thread is not None: | ||||
|             self._thread.join() | ||||
|         self._app_loop.close() | ||||
| 
 | ||||
|     def __call__( | ||||
|         self, | ||||
|         data: Optional[Iterable[_T]] = None, | ||||
|         label: AnyFormattedText = "", | ||||
|         remove_when_done: bool = False, | ||||
|         total: Optional[int] = None, | ||||
|     ) -> "ProgressBarCounter[_T]": | ||||
|         """ | ||||
|         Start a new counter. | ||||
| 
 | ||||
|         :param label: Title text or description for this progress. (This can be | ||||
|             formatted text as well). | ||||
|         :param remove_when_done: When `True`, hide this progress bar. | ||||
|         :param total: Specify the maximum value if it can't be calculated by | ||||
|             calling ``len``. | ||||
|         """ | ||||
|         counter = ProgressBarCounter( | ||||
|             self, data, label=label, remove_when_done=remove_when_done, total=total | ||||
|         ) | ||||
|         self.counters.append(counter) | ||||
|         return counter | ||||
| 
 | ||||
|     def invalidate(self) -> None: | ||||
|         self._app_loop.call_soon_threadsafe(self.app.invalidate) | ||||
| 
 | ||||
| 
 | ||||
| class _ProgressControl(UIControl): | ||||
|     """ | ||||
|     User control for the progress bar. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, progress_bar: ProgressBar, formatter: Formatter) -> None: | ||||
|         self.progress_bar = progress_bar | ||||
|         self.formatter = formatter | ||||
|         self._key_bindings = create_key_bindings() | ||||
| 
 | ||||
|     def create_content(self, width: int, height: int) -> UIContent: | ||||
|         items: List[StyleAndTextTuples] = [] | ||||
| 
 | ||||
|         for pr in self.progress_bar.counters: | ||||
|             try: | ||||
|                 text = self.formatter.format(self.progress_bar, pr, width) | ||||
|             except BaseException: | ||||
|                 traceback.print_exc() | ||||
|                 text = "ERROR" | ||||
| 
 | ||||
|             items.append(to_formatted_text(text)) | ||||
| 
 | ||||
|         def get_line(i: int) -> StyleAndTextTuples: | ||||
|             return items[i] | ||||
| 
 | ||||
|         return UIContent(get_line=get_line, line_count=len(items), show_cursor=False) | ||||
| 
 | ||||
|     def is_focusable(self) -> bool: | ||||
|         return True  # Make sure that the key bindings work. | ||||
| 
 | ||||
|     def get_key_bindings(self) -> KeyBindings: | ||||
|         return self._key_bindings | ||||
| 
 | ||||
| 
 | ||||
| _CounterItem = TypeVar("_CounterItem", covariant=True) | ||||
| 
 | ||||
| 
 | ||||
| class ProgressBarCounter(Generic[_CounterItem]): | ||||
|     """ | ||||
|     An individual counter (A progress bar can have multiple counters). | ||||
|     """ | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, | ||||
|         progress_bar: ProgressBar, | ||||
|         data: Optional[Iterable[_CounterItem]] = None, | ||||
|         label: AnyFormattedText = "", | ||||
|         remove_when_done: bool = False, | ||||
|         total: Optional[int] = None, | ||||
|     ) -> None: | ||||
| 
 | ||||
|         self.start_time = datetime.datetime.now() | ||||
|         self.stop_time: Optional[datetime.datetime] = None | ||||
|         self.progress_bar = progress_bar | ||||
|         self.data = data | ||||
|         self.items_completed = 0 | ||||
|         self.label = label | ||||
|         self.remove_when_done = remove_when_done | ||||
|         self._done = False | ||||
|         self.total: Optional[int] | ||||
| 
 | ||||
|         if total is None: | ||||
|             try: | ||||
|                 self.total = len(cast(Sized, data)) | ||||
|             except TypeError: | ||||
|                 self.total = None  # We don't know the total length. | ||||
|         else: | ||||
|             self.total = total | ||||
| 
 | ||||
|     def __iter__(self) -> Iterable[_CounterItem]: | ||||
|         if self.data is not None: | ||||
|             try: | ||||
|                 for item in self.data: | ||||
|                     yield item | ||||
|                     self.item_completed() | ||||
| 
 | ||||
|                 # Only done if we iterate to the very end. | ||||
|                 self.done = True | ||||
|             finally: | ||||
|                 # Ensure counter has stopped even if we did not iterate to the | ||||
|                 # end (e.g. break or exceptions). | ||||
|                 self.stopped = True | ||||
|         else: | ||||
|             raise NotImplementedError("No data defined to iterate over.") | ||||
| 
 | ||||
|     def item_completed(self) -> None: | ||||
|         """ | ||||
|         Start handling the next item. | ||||
| 
 | ||||
|         (Can be called manually in case we don't have a collection to loop through.) | ||||
|         """ | ||||
|         self.items_completed += 1 | ||||
|         self.progress_bar.invalidate() | ||||
| 
 | ||||
|     @property | ||||
|     def done(self) -> bool: | ||||
|         """Whether a counter has been completed. | ||||
| 
 | ||||
|         Done counter have been stopped (see stopped) and removed depending on | ||||
|         remove_when_done value. | ||||
| 
 | ||||
|         Contrast this with stopped. A stopped counter may be terminated before | ||||
|         100% completion. A done counter has reached its 100% completion. | ||||
|         """ | ||||
|         return self._done | ||||
| 
 | ||||
|     @done.setter | ||||
|     def done(self, value: bool) -> None: | ||||
|         self._done = value | ||||
|         self.stopped = value | ||||
| 
 | ||||
|         if value and self.remove_when_done: | ||||
|             self.progress_bar.counters.remove(self) | ||||
| 
 | ||||
|     @property | ||||
|     def stopped(self) -> bool: | ||||
|         """Whether a counter has been stopped. | ||||
| 
 | ||||
|         Stopped counters no longer have increasing time_elapsed. This distinction is | ||||
|         also used to prevent the Bar formatter with unknown totals from continuing to run. | ||||
| 
 | ||||
|         A stopped counter (but not done) can be used to signal that a given counter has | ||||
|         encountered an error but allows other counters to continue | ||||
|         (e.g. download X of Y failed). Given how only done counters are removed | ||||
|         (see remove_when_done) this can help aggregate failures from a large number of | ||||
|         successes. | ||||
| 
 | ||||
|         Contrast this with done. A done counter has reached its 100% completion. | ||||
|         A stopped counter may be terminated before 100% completion. | ||||
|         """ | ||||
|         return self.stop_time is not None | ||||
| 
 | ||||
|     @stopped.setter | ||||
|     def stopped(self, value: bool) -> None: | ||||
|         if value: | ||||
|             # This counter has not already been stopped. | ||||
|             if not self.stop_time: | ||||
|                 self.stop_time = datetime.datetime.now() | ||||
|         else: | ||||
|             # Clearing any previously set stop_time. | ||||
|             self.stop_time = None | ||||
| 
 | ||||
|     @property | ||||
|     def percentage(self) -> float: | ||||
|         if self.total is None: | ||||
|             return 0 | ||||
|         else: | ||||
|             return self.items_completed * 100 / max(self.total, 1) | ||||
| 
 | ||||
|     @property | ||||
|     def time_elapsed(self) -> datetime.timedelta: | ||||
|         """ | ||||
|         Return how much time has been elapsed since the start. | ||||
|         """ | ||||
|         if self.stop_time is None: | ||||
|             return datetime.datetime.now() - self.start_time | ||||
|         else: | ||||
|             return self.stop_time - self.start_time | ||||
| 
 | ||||
|     @property | ||||
|     def time_left(self) -> Optional[datetime.timedelta]: | ||||
|         """ | ||||
|         Timedelta representing the time left. | ||||
|         """ | ||||
|         if self.total is None or not self.percentage: | ||||
|             return None | ||||
|         elif self.done or self.stopped: | ||||
|             return datetime.timedelta(0) | ||||
|         else: | ||||
|             return self.time_elapsed * (100 - self.percentage) / self.percentage | ||||
|  | @ -0,0 +1,436 @@ | |||
| """ | ||||
| Formatter classes for the progress bar. | ||||
| Each progress bar consists of a list of these formatters. | ||||
| """ | ||||
| import datetime | ||||
| import time | ||||
| from abc import ABCMeta, abstractmethod | ||||
| from typing import TYPE_CHECKING, List, Tuple | ||||
| 
 | ||||
| from prompt_toolkit.formatted_text import ( | ||||
|     HTML, | ||||
|     AnyFormattedText, | ||||
|     StyleAndTextTuples, | ||||
|     to_formatted_text, | ||||
| ) | ||||
| from prompt_toolkit.formatted_text.utils import fragment_list_width | ||||
| from prompt_toolkit.layout.dimension import AnyDimension, D | ||||
| from prompt_toolkit.layout.utils import explode_text_fragments | ||||
| from prompt_toolkit.utils import get_cwidth | ||||
| 
 | ||||
| if TYPE_CHECKING: | ||||
|     from .base import ProgressBar, ProgressBarCounter | ||||
| 
 | ||||
| __all__ = [ | ||||
|     "Formatter", | ||||
|     "Text", | ||||
|     "Label", | ||||
|     "Percentage", | ||||
|     "Bar", | ||||
|     "Progress", | ||||
|     "TimeElapsed", | ||||
|     "TimeLeft", | ||||
|     "IterationsPerSecond", | ||||
|     "SpinningWheel", | ||||
|     "Rainbow", | ||||
|     "create_default_formatters", | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| class Formatter(metaclass=ABCMeta): | ||||
|     """ | ||||
|     Base class for any formatter. | ||||
|     """ | ||||
| 
 | ||||
|     @abstractmethod | ||||
|     def format( | ||||
|         self, | ||||
|         progress_bar: "ProgressBar", | ||||
|         progress: "ProgressBarCounter[object]", | ||||
|         width: int, | ||||
|     ) -> AnyFormattedText: | ||||
|         pass | ||||
| 
 | ||||
|     def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: | ||||
|         return D() | ||||
| 
 | ||||
| 
 | ||||
| class Text(Formatter): | ||||
|     """ | ||||
|     Display plain text. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, text: AnyFormattedText, style: str = "") -> None: | ||||
|         self.text = to_formatted_text(text, style=style) | ||||
| 
 | ||||
|     def format( | ||||
|         self, | ||||
|         progress_bar: "ProgressBar", | ||||
|         progress: "ProgressBarCounter[object]", | ||||
|         width: int, | ||||
|     ) -> AnyFormattedText: | ||||
|         return self.text | ||||
| 
 | ||||
|     def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: | ||||
|         return fragment_list_width(self.text) | ||||
| 
 | ||||
| 
 | ||||
| class Label(Formatter): | ||||
|     """ | ||||
|     Display the name of the current task. | ||||
| 
 | ||||
|     :param width: If a `width` is given, use this width. Scroll the text if it | ||||
|         doesn't fit in this width. | ||||
|     :param suffix: String suffix to be added after the task name, e.g. ': '. | ||||
|         If no task name was given, no suffix will be added. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, width: AnyDimension = None, suffix: str = "") -> None: | ||||
|         self.width = width | ||||
|         self.suffix = suffix | ||||
| 
 | ||||
|     def _add_suffix(self, label: AnyFormattedText) -> StyleAndTextTuples: | ||||
|         label = to_formatted_text(label, style="class:label") | ||||
|         return label + [("", self.suffix)] | ||||
| 
 | ||||
|     def format( | ||||
|         self, | ||||
|         progress_bar: "ProgressBar", | ||||
|         progress: "ProgressBarCounter[object]", | ||||
|         width: int, | ||||
|     ) -> AnyFormattedText: | ||||
| 
 | ||||
|         label = self._add_suffix(progress.label) | ||||
|         cwidth = fragment_list_width(label) | ||||
| 
 | ||||
|         if cwidth > width: | ||||
|             # It doesn't fit -> scroll task name. | ||||
|             label = explode_text_fragments(label) | ||||
|             max_scroll = cwidth - width | ||||
|             current_scroll = int(time.time() * 3 % max_scroll) | ||||
|             label = label[current_scroll:] | ||||
| 
 | ||||
|         return label | ||||
| 
 | ||||
|     def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: | ||||
|         if self.width: | ||||
|             return self.width | ||||
| 
 | ||||
|         all_labels = [self._add_suffix(c.label) for c in progress_bar.counters] | ||||
|         if all_labels: | ||||
|             max_widths = max(fragment_list_width(l) for l in all_labels) | ||||
|             return D(preferred=max_widths, max=max_widths) | ||||
|         else: | ||||
|             return D() | ||||
| 
 | ||||
| 
 | ||||
| class Percentage(Formatter): | ||||
|     """ | ||||
|     Display the progress as a percentage. | ||||
|     """ | ||||
| 
 | ||||
|     template = "<percentage>{percentage:>5}%</percentage>" | ||||
| 
 | ||||
|     def format( | ||||
|         self, | ||||
|         progress_bar: "ProgressBar", | ||||
|         progress: "ProgressBarCounter[object]", | ||||
|         width: int, | ||||
|     ) -> AnyFormattedText: | ||||
| 
 | ||||
|         return HTML(self.template).format(percentage=round(progress.percentage, 1)) | ||||
| 
 | ||||
|     def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: | ||||
|         return D.exact(6) | ||||
| 
 | ||||
| 
 | ||||
| class Bar(Formatter): | ||||
|     """ | ||||
|     Display the progress bar itself. | ||||
|     """ | ||||
| 
 | ||||
|     template = "<bar>{start}<bar-a>{bar_a}</bar-a><bar-b>{bar_b}</bar-b><bar-c>{bar_c}</bar-c>{end}</bar>" | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, | ||||
|         start: str = "[", | ||||
|         end: str = "]", | ||||
|         sym_a: str = "=", | ||||
|         sym_b: str = ">", | ||||
|         sym_c: str = " ", | ||||
|         unknown: str = "#", | ||||
|     ) -> None: | ||||
| 
 | ||||
|         assert len(sym_a) == 1 and get_cwidth(sym_a) == 1 | ||||
|         assert len(sym_c) == 1 and get_cwidth(sym_c) == 1 | ||||
| 
 | ||||
|         self.start = start | ||||
|         self.end = end | ||||
|         self.sym_a = sym_a | ||||
|         self.sym_b = sym_b | ||||
|         self.sym_c = sym_c | ||||
|         self.unknown = unknown | ||||
| 
 | ||||
|     def format( | ||||
|         self, | ||||
|         progress_bar: "ProgressBar", | ||||
|         progress: "ProgressBarCounter[object]", | ||||
|         width: int, | ||||
|     ) -> AnyFormattedText: | ||||
|         if progress.done or progress.total or progress.stopped: | ||||
|             sym_a, sym_b, sym_c = self.sym_a, self.sym_b, self.sym_c | ||||
| 
 | ||||
|             # Compute pb_a based on done, total, or stopped states. | ||||
|             if progress.done: | ||||
|                 # 100% completed irrelevant of how much was actually marked as completed. | ||||
|                 percent = 1.0 | ||||
|             else: | ||||
|                 # Show percentage completed. | ||||
|                 percent = progress.percentage / 100 | ||||
|         else: | ||||
|             # Total is unknown and bar is still running. | ||||
|             sym_a, sym_b, sym_c = self.sym_c, self.unknown, self.sym_c | ||||
| 
 | ||||
|             # Compute percent based on the time. | ||||
|             percent = time.time() * 20 % 100 / 100 | ||||
| 
 | ||||
|         # Subtract left, sym_b, and right. | ||||
|         width -= get_cwidth(self.start + sym_b + self.end) | ||||
| 
 | ||||
|         # Scale percent by width | ||||
|         pb_a = int(percent * width) | ||||
|         bar_a = sym_a * pb_a | ||||
|         bar_b = sym_b | ||||
|         bar_c = sym_c * (width - pb_a) | ||||
| 
 | ||||
|         return HTML(self.template).format( | ||||
|             start=self.start, end=self.end, bar_a=bar_a, bar_b=bar_b, bar_c=bar_c | ||||
|         ) | ||||
| 
 | ||||
|     def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: | ||||
|         return D(min=9) | ||||
| 
 | ||||
| 
 | ||||
| class Progress(Formatter): | ||||
|     """ | ||||
|     Display the progress as text.  E.g. "8/20" | ||||
|     """ | ||||
| 
 | ||||
|     template = "<current>{current:>3}</current>/<total>{total:>3}</total>" | ||||
| 
 | ||||
|     def format( | ||||
|         self, | ||||
|         progress_bar: "ProgressBar", | ||||
|         progress: "ProgressBarCounter[object]", | ||||
|         width: int, | ||||
|     ) -> AnyFormattedText: | ||||
| 
 | ||||
|         return HTML(self.template).format( | ||||
|             current=progress.items_completed, total=progress.total or "?" | ||||
|         ) | ||||
| 
 | ||||
|     def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: | ||||
|         all_lengths = [ | ||||
|             len("{0:>3}".format(c.total or "?")) for c in progress_bar.counters | ||||
|         ] | ||||
|         all_lengths.append(1) | ||||
|         return D.exact(max(all_lengths) * 2 + 1) | ||||
| 
 | ||||
| 
 | ||||
| def _format_timedelta(timedelta: datetime.timedelta) -> str: | ||||
|     """ | ||||
|     Return hh:mm:ss, or mm:ss if the amount of hours is zero. | ||||
|     """ | ||||
|     result = "{0}".format(timedelta).split(".")[0] | ||||
|     if result.startswith("0:"): | ||||
|         result = result[2:] | ||||
|     return result | ||||
| 
 | ||||
| 
 | ||||
| class TimeElapsed(Formatter): | ||||
|     """ | ||||
|     Display the elapsed time. | ||||
|     """ | ||||
| 
 | ||||
|     def format( | ||||
|         self, | ||||
|         progress_bar: "ProgressBar", | ||||
|         progress: "ProgressBarCounter[object]", | ||||
|         width: int, | ||||
|     ) -> AnyFormattedText: | ||||
| 
 | ||||
|         text = _format_timedelta(progress.time_elapsed).rjust(width) | ||||
|         return HTML("<time-elapsed>{time_elapsed}</time-elapsed>").format( | ||||
|             time_elapsed=text | ||||
|         ) | ||||
| 
 | ||||
|     def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: | ||||
|         all_values = [ | ||||
|             len(_format_timedelta(c.time_elapsed)) for c in progress_bar.counters | ||||
|         ] | ||||
|         if all_values: | ||||
|             return max(all_values) | ||||
|         return 0 | ||||
| 
 | ||||
| 
 | ||||
| class TimeLeft(Formatter): | ||||
|     """ | ||||
|     Display the time left. | ||||
|     """ | ||||
| 
 | ||||
|     template = "<time-left>{time_left}</time-left>" | ||||
|     unknown = "?:??:??" | ||||
| 
 | ||||
|     def format( | ||||
|         self, | ||||
|         progress_bar: "ProgressBar", | ||||
|         progress: "ProgressBarCounter[object]", | ||||
|         width: int, | ||||
|     ) -> AnyFormattedText: | ||||
| 
 | ||||
|         time_left = progress.time_left | ||||
|         if time_left is not None: | ||||
|             formatted_time_left = _format_timedelta(time_left) | ||||
|         else: | ||||
|             formatted_time_left = self.unknown | ||||
| 
 | ||||
|         return HTML(self.template).format(time_left=formatted_time_left.rjust(width)) | ||||
| 
 | ||||
|     def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: | ||||
|         all_values = [ | ||||
|             len(_format_timedelta(c.time_left)) if c.time_left is not None else 7 | ||||
|             for c in progress_bar.counters | ||||
|         ] | ||||
|         if all_values: | ||||
|             return max(all_values) | ||||
|         return 0 | ||||
| 
 | ||||
| 
 | ||||
| class IterationsPerSecond(Formatter): | ||||
|     """ | ||||
|     Display the iterations per second. | ||||
|     """ | ||||
| 
 | ||||
|     template = ( | ||||
|         "<iterations-per-second>{iterations_per_second:.2f}</iterations-per-second>" | ||||
|     ) | ||||
| 
 | ||||
|     def format( | ||||
|         self, | ||||
|         progress_bar: "ProgressBar", | ||||
|         progress: "ProgressBarCounter[object]", | ||||
|         width: int, | ||||
|     ) -> AnyFormattedText: | ||||
| 
 | ||||
|         value = progress.items_completed / progress.time_elapsed.total_seconds() | ||||
|         return HTML(self.template.format(iterations_per_second=value)) | ||||
| 
 | ||||
|     def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: | ||||
|         all_values = [ | ||||
|             len("{0:.2f}".format(c.items_completed / c.time_elapsed.total_seconds())) | ||||
|             for c in progress_bar.counters | ||||
|         ] | ||||
|         if all_values: | ||||
|             return max(all_values) | ||||
|         return 0 | ||||
| 
 | ||||
| 
 | ||||
| class SpinningWheel(Formatter): | ||||
|     """ | ||||
|     Display a spinning wheel. | ||||
|     """ | ||||
| 
 | ||||
|     characters = r"/-\|" | ||||
| 
 | ||||
|     def format( | ||||
|         self, | ||||
|         progress_bar: "ProgressBar", | ||||
|         progress: "ProgressBarCounter[object]", | ||||
|         width: int, | ||||
|     ) -> AnyFormattedText: | ||||
| 
 | ||||
|         index = int(time.time() * 3) % len(self.characters) | ||||
|         return HTML("<spinning-wheel>{0}</spinning-wheel>").format( | ||||
|             self.characters[index] | ||||
|         ) | ||||
| 
 | ||||
|     def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: | ||||
|         return D.exact(1) | ||||
| 
 | ||||
| 
 | ||||
| def _hue_to_rgb(hue: float) -> Tuple[int, int, int]: | ||||
|     """ | ||||
|     Take hue between 0 and 1, return (r, g, b). | ||||
|     """ | ||||
|     i = int(hue * 6.0) | ||||
|     f = (hue * 6.0) - i | ||||
| 
 | ||||
|     q = int(255 * (1.0 - f)) | ||||
|     t = int(255 * (1.0 - (1.0 - f))) | ||||
| 
 | ||||
|     i %= 6 | ||||
| 
 | ||||
|     return [ | ||||
|         (255, t, 0), | ||||
|         (q, 255, 0), | ||||
|         (0, 255, t), | ||||
|         (0, q, 255), | ||||
|         (t, 0, 255), | ||||
|         (255, 0, q), | ||||
|     ][i] | ||||
| 
 | ||||
| 
 | ||||
| class Rainbow(Formatter): | ||||
|     """ | ||||
|     For the fun. Add rainbow colors to any of the other formatters. | ||||
|     """ | ||||
| 
 | ||||
|     colors = ["#%.2x%.2x%.2x" % _hue_to_rgb(h / 100.0) for h in range(0, 100)] | ||||
| 
 | ||||
|     def __init__(self, formatter: Formatter) -> None: | ||||
|         self.formatter = formatter | ||||
| 
 | ||||
|     def format( | ||||
|         self, | ||||
|         progress_bar: "ProgressBar", | ||||
|         progress: "ProgressBarCounter[object]", | ||||
|         width: int, | ||||
|     ) -> AnyFormattedText: | ||||
| 
 | ||||
|         # Get formatted text from nested formatter, and explode it in | ||||
|         # text/style tuples. | ||||
|         result = self.formatter.format(progress_bar, progress, width) | ||||
|         result = explode_text_fragments(to_formatted_text(result)) | ||||
| 
 | ||||
|         # Insert colors. | ||||
|         result2: StyleAndTextTuples = [] | ||||
|         shift = int(time.time() * 3) % len(self.colors) | ||||
| 
 | ||||
|         for i, (style, text, *_) in enumerate(result): | ||||
|             result2.append( | ||||
|                 (style + " " + self.colors[(i + shift) % len(self.colors)], text) | ||||
|             ) | ||||
|         return result2 | ||||
| 
 | ||||
|     def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: | ||||
|         return self.formatter.get_width(progress_bar) | ||||
| 
 | ||||
| 
 | ||||
| def create_default_formatters() -> List[Formatter]: | ||||
|     """ | ||||
|     Return the list of default formatters. | ||||
|     """ | ||||
|     return [ | ||||
|         Label(), | ||||
|         Text(" "), | ||||
|         Percentage(), | ||||
|         Text(" "), | ||||
|         Bar(), | ||||
|         Text(" "), | ||||
|         Progress(), | ||||
|         Text(" "), | ||||
|         Text("eta [", style="class:time-left"), | ||||
|         TimeLeft(), | ||||
|         Text("]", style="class:time-left"), | ||||
|         Text(" "), | ||||
|     ] | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue