Uploaded Test files

This commit is contained in:
Batuhan Berk Başoğlu 2020-11-12 11:05:57 -05:00
parent f584ad9d97
commit 2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions

View file

@ -0,0 +1,44 @@
from .dialogs import (
button_dialog,
checkboxlist_dialog,
input_dialog,
message_dialog,
progress_dialog,
radiolist_dialog,
yes_no_dialog,
)
from .progress_bar import ProgressBar, ProgressBarCounter
from .prompt import (
CompleteStyle,
PromptSession,
confirm,
create_confirm_session,
prompt,
)
from .utils import clear, clear_title, print_container, print_formatted_text, set_title
__all__ = [
# Dialogs.
"input_dialog",
"message_dialog",
"progress_dialog",
"checkboxlist_dialog",
"radiolist_dialog",
"yes_no_dialog",
"button_dialog",
# Prompts.
"PromptSession",
"prompt",
"confirm",
"create_confirm_session",
"CompleteStyle",
# Progress bars.
"ProgressBar",
"ProgressBarCounter",
# Utils.
"clear",
"clear_title",
"print_container",
"print_formatted_text",
"set_title",
]

View file

@ -0,0 +1,324 @@
import functools
from asyncio import get_event_loop
from typing import Any, Callable, List, Optional, Tuple, TypeVar
from prompt_toolkit.application import Application
from prompt_toolkit.application.current import get_app
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.completion import Completer
from prompt_toolkit.eventloop import run_in_executor_with_context
from prompt_toolkit.filters import FilterOrBool
from prompt_toolkit.formatted_text import AnyFormattedText
from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous
from prompt_toolkit.key_binding.defaults import load_key_bindings
from prompt_toolkit.key_binding.key_bindings import KeyBindings, merge_key_bindings
from prompt_toolkit.layout import Layout
from prompt_toolkit.layout.containers import AnyContainer, HSplit
from prompt_toolkit.layout.dimension import Dimension as D
from prompt_toolkit.styles import BaseStyle
from prompt_toolkit.validation import Validator
from prompt_toolkit.widgets import (
Box,
Button,
CheckboxList,
Dialog,
Label,
ProgressBar,
RadioList,
TextArea,
ValidationToolbar,
)
__all__ = [
"yes_no_dialog",
"button_dialog",
"input_dialog",
"message_dialog",
"radiolist_dialog",
"checkboxlist_dialog",
"progress_dialog",
]
def yes_no_dialog(
title: AnyFormattedText = "",
text: AnyFormattedText = "",
yes_text: str = "Yes",
no_text: str = "No",
style: Optional[BaseStyle] = None,
) -> Application[bool]:
"""
Display a Yes/No dialog.
Return a boolean.
"""
def yes_handler() -> None:
get_app().exit(result=True)
def no_handler() -> None:
get_app().exit(result=False)
dialog = Dialog(
title=title,
body=Label(text=text, dont_extend_height=True),
buttons=[
Button(text=yes_text, handler=yes_handler),
Button(text=no_text, handler=no_handler),
],
with_background=True,
)
return _create_app(dialog, style)
_T = TypeVar("_T")
def button_dialog(
title: AnyFormattedText = "",
text: AnyFormattedText = "",
buttons: List[Tuple[str, _T]] = [],
style: Optional[BaseStyle] = None,
) -> Application[_T]:
"""
Display a dialog with button choices (given as a list of tuples).
Return the value associated with button.
"""
def button_handler(v: _T) -> None:
get_app().exit(result=v)
dialog = Dialog(
title=title,
body=Label(text=text, dont_extend_height=True),
buttons=[
Button(text=t, handler=functools.partial(button_handler, v))
for t, v in buttons
],
with_background=True,
)
return _create_app(dialog, style)
def input_dialog(
title: AnyFormattedText = "",
text: AnyFormattedText = "",
ok_text: str = "OK",
cancel_text: str = "Cancel",
completer: Optional[Completer] = None,
validator: Optional[Validator] = None,
password: FilterOrBool = False,
style: Optional[BaseStyle] = None,
) -> Application[str]:
"""
Display a text input box.
Return the given text, or None when cancelled.
"""
def accept(buf: Buffer) -> bool:
get_app().layout.focus(ok_button)
return True # Keep text.
def ok_handler() -> None:
get_app().exit(result=textfield.text)
ok_button = Button(text=ok_text, handler=ok_handler)
cancel_button = Button(text=cancel_text, handler=_return_none)
textfield = TextArea(
multiline=False,
password=password,
completer=completer,
validator=validator,
accept_handler=accept,
)
dialog = Dialog(
title=title,
body=HSplit(
[
Label(text=text, dont_extend_height=True),
textfield,
ValidationToolbar(),
],
padding=D(preferred=1, max=1),
),
buttons=[ok_button, cancel_button],
with_background=True,
)
return _create_app(dialog, style)
def message_dialog(
title: AnyFormattedText = "",
text: AnyFormattedText = "",
ok_text: str = "Ok",
style: Optional[BaseStyle] = None,
) -> Application[None]:
"""
Display a simple message box and wait until the user presses enter.
"""
dialog = Dialog(
title=title,
body=Label(text=text, dont_extend_height=True),
buttons=[Button(text=ok_text, handler=_return_none)],
with_background=True,
)
return _create_app(dialog, style)
def radiolist_dialog(
title: AnyFormattedText = "",
text: AnyFormattedText = "",
ok_text: str = "Ok",
cancel_text: str = "Cancel",
values: Optional[List[Tuple[_T, AnyFormattedText]]] = None,
style: Optional[BaseStyle] = None,
) -> Application[_T]:
"""
Display a simple list of element the user can choose amongst.
Only one element can be selected at a time using Arrow keys and Enter.
The focus can be moved between the list and the Ok/Cancel button with tab.
"""
if values is None:
values = []
def ok_handler() -> None:
get_app().exit(result=radio_list.current_value)
radio_list = RadioList(values)
dialog = Dialog(
title=title,
body=HSplit(
[Label(text=text, dont_extend_height=True), radio_list],
padding=1,
),
buttons=[
Button(text=ok_text, handler=ok_handler),
Button(text=cancel_text, handler=_return_none),
],
with_background=True,
)
return _create_app(dialog, style)
def checkboxlist_dialog(
title: AnyFormattedText = "",
text: AnyFormattedText = "",
ok_text: str = "Ok",
cancel_text: str = "Cancel",
values: Optional[List[Tuple[_T, AnyFormattedText]]] = None,
style: Optional[BaseStyle] = None,
) -> Application[List[_T]]:
"""
Display a simple list of element the user can choose multiple values amongst.
Several elements can be selected at a time using Arrow keys and Enter.
The focus can be moved between the list and the Ok/Cancel button with tab.
"""
if values is None:
values = []
def ok_handler() -> None:
get_app().exit(result=cb_list.current_values)
cb_list = CheckboxList(values)
dialog = Dialog(
title=title,
body=HSplit(
[Label(text=text, dont_extend_height=True), cb_list],
padding=1,
),
buttons=[
Button(text=ok_text, handler=ok_handler),
Button(text=cancel_text, handler=_return_none),
],
with_background=True,
)
return _create_app(dialog, style)
def progress_dialog(
title: AnyFormattedText = "",
text: AnyFormattedText = "",
run_callback: Callable[[Callable[[int], None], Callable[[str], None]], None] = (
lambda *a: None
),
style: Optional[BaseStyle] = None,
) -> Application[None]:
"""
:param run_callback: A function that receives as input a `set_percentage`
function and it does the work.
"""
loop = get_event_loop()
progressbar = ProgressBar()
text_area = TextArea(
focusable=False,
# Prefer this text area as big as possible, to avoid having a window
# that keeps resizing when we add text to it.
height=D(preferred=10 ** 10),
)
dialog = Dialog(
body=HSplit(
[
Box(Label(text=text)),
Box(text_area, padding=D.exact(1)),
progressbar,
]
),
title=title,
with_background=True,
)
app = _create_app(dialog, style)
def set_percentage(value: int) -> None:
progressbar.percentage = int(value)
app.invalidate()
def log_text(text: str) -> None:
loop.call_soon_threadsafe(text_area.buffer.insert_text, text)
app.invalidate()
# Run the callback in the executor. When done, set a return value for the
# UI, so that it quits.
def start() -> None:
try:
run_callback(set_percentage, log_text)
finally:
app.exit()
def pre_run() -> None:
run_in_executor_with_context(start)
app.pre_run_callables.append(pre_run)
return app
def _create_app(dialog: AnyContainer, style: Optional[BaseStyle]) -> Application[Any]:
# Key bindings.
bindings = KeyBindings()
bindings.add("tab")(focus_next)
bindings.add("s-tab")(focus_previous)
return Application(
layout=Layout(dialog),
key_bindings=merge_key_bindings([load_key_bindings(), bindings]),
mouse_support=True,
style=style,
full_screen=True,
)
def _return_none() -> None:
" Button handler that returns None. "
get_app().exit()

View file

@ -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",
]

View file

@ -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

View file

@ -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(" "),
]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,198 @@
from asyncio import get_event_loop
from typing import TYPE_CHECKING, Any, Optional, TextIO
from prompt_toolkit.application import Application
from prompt_toolkit.application.current import get_app_session
from prompt_toolkit.formatted_text import (
FormattedText,
StyleAndTextTuples,
to_formatted_text,
)
from prompt_toolkit.input import DummyInput
from prompt_toolkit.layout import Layout
from prompt_toolkit.output import ColorDepth, Output
from prompt_toolkit.output.defaults import create_output
from prompt_toolkit.renderer import (
print_formatted_text as renderer_print_formatted_text,
)
from prompt_toolkit.styles import (
BaseStyle,
StyleTransformation,
default_pygments_style,
default_ui_style,
merge_styles,
)
if TYPE_CHECKING:
from prompt_toolkit.layout.containers import Container
__all__ = [
"print_formatted_text",
"print_container",
"clear",
"set_title",
"clear_title",
]
def print_formatted_text(
*values: Any,
sep: str = " ",
end: str = "\n",
file: Optional[TextIO] = None,
flush: bool = False,
style: Optional[BaseStyle] = None,
output: Optional[Output] = None,
color_depth: Optional[ColorDepth] = None,
style_transformation: Optional[StyleTransformation] = None,
include_default_pygments_style: bool = True,
) -> None:
"""
::
print_formatted_text(*values, sep=' ', end='\\n', file=None, flush=False, style=None, output=None)
Print text to stdout. This is supposed to be compatible with Python's print
function, but supports printing of formatted text. You can pass a
:class:`~prompt_toolkit.formatted_text.FormattedText`,
:class:`~prompt_toolkit.formatted_text.HTML` or
:class:`~prompt_toolkit.formatted_text.ANSI` object to print formatted
text.
* Print HTML as follows::
print_formatted_text(HTML('<i>Some italic text</i> <ansired>This is red!</ansired>'))
style = Style.from_dict({
'hello': '#ff0066',
'world': '#884444 italic',
})
print_formatted_text(HTML('<hello>Hello</hello> <world>world</world>!'), style=style)
* Print a list of (style_str, text) tuples in the given style to the
output. E.g.::
style = Style.from_dict({
'hello': '#ff0066',
'world': '#884444 italic',
})
fragments = FormattedText([
('class:hello', 'Hello'),
('class:world', 'World'),
])
print_formatted_text(fragments, style=style)
If you want to print a list of Pygments tokens, wrap it in
:class:`~prompt_toolkit.formatted_text.PygmentsTokens` to do the
conversion.
:param values: Any kind of printable object, or formatted string.
:param sep: String inserted between values, default a space.
:param end: String appended after the last value, default a newline.
:param style: :class:`.Style` instance for the color scheme.
:param include_default_pygments_style: `bool`. Include the default Pygments
style when set to `True` (the default).
"""
assert not (output and file)
# Build/merge style.
styles = [default_ui_style()]
if include_default_pygments_style:
styles.append(default_pygments_style())
if style:
styles.append(style)
merged_style = merge_styles(styles)
# Create Output object.
if output is None:
if file:
output = create_output(stdout=file)
else:
output = get_app_session().output
assert isinstance(output, Output)
# Get color depth.
color_depth = color_depth or output.get_default_color_depth()
# Merges values.
def to_text(val: Any) -> StyleAndTextTuples:
# Normal lists which are not instances of `FormattedText` are
# considered plain text.
if isinstance(val, list) and not isinstance(val, FormattedText):
return to_formatted_text("{0}".format(val))
return to_formatted_text(val, auto_convert=True)
fragments = []
for i, value in enumerate(values):
fragments.extend(to_text(value))
if sep and i != len(values) - 1:
fragments.extend(to_text(sep))
fragments.extend(to_text(end))
# Print output.
renderer_print_formatted_text(
output,
fragments,
merged_style,
color_depth=color_depth,
style_transformation=style_transformation,
)
# Flush the output stream.
if flush:
output.flush()
def print_container(container: "Container", file: Optional[TextIO] = None) -> None:
"""
Print any layout to the output in a non-interactive way.
Example usage::
from prompt_toolkit.widgets import Frame, TextArea
print_container(
Frame(TextArea(text='Hello world!')))
"""
if file:
output = create_output(stdout=file)
else:
output = get_app_session().output
def exit_immediately() -> None:
# Use `call_from_executor` to exit "soon", so that we still render one
# initial time, before exiting the application.
get_event_loop().call_soon(lambda: app.exit())
app: Application[None] = Application(
layout=Layout(container=container), output=output, input=DummyInput()
)
app.run(pre_run=exit_immediately)
def clear() -> None:
"""
Clear the screen.
"""
output = get_app_session().output
output.erase_screen()
output.cursor_goto(0, 0)
output.flush()
def set_title(text: str) -> None:
"""
Set the terminal title.
"""
output = get_app_session().output
output.set_title(text)
def clear_title() -> None:
"""
Erase the current title.
"""
set_title("")