From a34b9202f5e1223e87c983f38acca05b2c4f94d3 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Fri, 12 Nov 2021 12:55:11 +1100 Subject: [PATCH 01/41] feat: init number prompt --- CHANGELOG.md | 6 ++ InquirerPy/enum.py | 1 + InquirerPy/inquirer.py | 1 + InquirerPy/prompts/__init__.py | 1 + InquirerPy/prompts/number.py | 156 +++++++++++++++++++++++++++++++++ examples/alternate/number.py | 3 + 6 files changed, 168 insertions(+) create mode 100644 InquirerPy/prompts/number.py create mode 100644 examples/alternate/number.py diff --git a/CHANGELOG.md b/CHANGELOG.md index bad7b6c..89c1a09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Notable changes are documented in this file. +## dev + +### Fixed + +- Fixed InvalidArgument raised for callable default + ## 0.3.0 (12/10/2021) **New Documentation: [inquirerpy.readthedocs.io](https://inquirerpy.readthedocs.io/en/latest/)** diff --git a/InquirerPy/enum.py b/InquirerPy/enum.py index 02dbbb7..71d46bf 100644 --- a/InquirerPy/enum.py +++ b/InquirerPy/enum.py @@ -4,3 +4,4 @@ INQUIRERPY_POINTER_SEQUENCE: str = "\u276f" INQUIRERPY_FILL_CIRCLE_SEQUENCE: str = "\u25c9" INQUIRERPY_EMPTY_CIRCLE_SEQUENCE: str = "\u25cb" +INQUIRERPY_QMARK_SEQUENCE: str = "\u003f" diff --git a/InquirerPy/inquirer.py b/InquirerPy/inquirer.py index 85eea8c..3553a3f 100644 --- a/InquirerPy/inquirer.py +++ b/InquirerPy/inquirer.py @@ -12,5 +12,6 @@ from InquirerPy.prompts import FuzzyPrompt as fuzzy from InquirerPy.prompts import InputPrompt as text from InquirerPy.prompts import ListPrompt as select +from InquirerPy.prompts import NumberPrompt as number from InquirerPy.prompts import RawlistPrompt as rawlist from InquirerPy.prompts import SecretPrompt as secret diff --git a/InquirerPy/prompts/__init__.py b/InquirerPy/prompts/__init__.py index 94c3a85..a04cf64 100644 --- a/InquirerPy/prompts/__init__.py +++ b/InquirerPy/prompts/__init__.py @@ -6,5 +6,6 @@ from InquirerPy.prompts.fuzzy import FuzzyPrompt from InquirerPy.prompts.input import InputPrompt from InquirerPy.prompts.list import ListPrompt +from InquirerPy.prompts.number import NumberPrompt from InquirerPy.prompts.rawlist import RawlistPrompt from InquirerPy.prompts.secret import SecretPrompt diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py new file mode 100644 index 0000000..f36184c --- /dev/null +++ b/InquirerPy/prompts/number.py @@ -0,0 +1,156 @@ +"""Module contains the class to create a number prompt.""" +from typing import Any, Callable, Union, cast + +from prompt_toolkit.filters.cli import IsDone +from prompt_toolkit.layout.containers import ( + Float, + FloatContainer, + HSplit, + VSplit, + Window, +) +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.dimension import LayoutDimension + +from InquirerPy.base.complex import BaseComplexPrompt +from InquirerPy.containers.validation import ValidationWindow +from InquirerPy.enum import INQUIRERPY_QMARK_SEQUENCE +from InquirerPy.exceptions import InvalidArgument +from InquirerPy.utils import ( + InquirerPyDefault, + InquirerPyKeybindings, + InquirerPyMessage, + InquirerPySessionResult, + InquirerPyStyle, + InquirerPyValidate, +) + +__all__ = ["NumberPrompt"] + + +class NumberPrompt(BaseComplexPrompt): + """Create a input prompts that only takes number as input.""" + + def __init__( + self, + message: InquirerPyMessage, + style: InquirerPyStyle = None, + vi_mode: bool = False, + default: InquirerPyDefault = "", + float_allowed: bool = False, + max_allowed: Union[int, float] = None, + min_allowed: Union[int, float] = None, + qmark: str = INQUIRERPY_QMARK_SEQUENCE, + amark: str = "?", + instruction: str = "", + long_instruction: str = "", + validate: InquirerPyValidate = None, + invalid_message: str = "Invalid input", + transformer: Callable[[str], Any] = None, + filter: Callable[[str], Any] = None, + keybindings: InquirerPyKeybindings = None, + wrap_lines: bool = True, + raise_keyboard_interrupt: bool = True, + mandatory: bool = True, + mandatory_message: str = "Mandatory prompt", + session_result: InquirerPySessionResult = None, + ) -> None: + super().__init__( + message=message, + style=style, + vi_mode=vi_mode, + qmark=qmark, + amark=amark, + instruction=instruction, + long_instruction=long_instruction, + validate=validate, + invalid_message=invalid_message, + transformer=transformer, + filter=filter, + wrap_lines=wrap_lines, + raise_keyboard_interrupt=raise_keyboard_interrupt, + mandatory=mandatory, + mandatory_message=mandatory_message, + session_result=session_result, + ) + + self._float = float_allowed + self._max = max_allowed + self._min = min_allowed + + if isinstance(default, Callable): + default = cast(Callable, default)(session_result) + if self._float and not isinstance(default, float): + raise InvalidArgument( + f"{type(self).__name__} argument 'default' should return type of float" + ) + elif not isinstance(default, int): + raise InvalidArgument( + f"{type(self).__name__} argument 'default' should return type of int" + ) + + if keybindings is None: + keybindings = {} + self.kb_maps = { + "down": [ + {"key": "down"}, + {"key": "c-n", "filter": ~self._is_vim_edit}, + {"key": "j", "filter": self._is_vim_edit}, + ], + "up": [ + {"key": "up"}, + {"key": "c-p", "filter": ~self._is_vim_edit}, + {"key": "k", "filter": self._is_vim_edit}, + ], + "left": [{"key": "left"}], + "right": [{"key": "right"}], + **keybindings, + } + self.kb_func_lookup = { + "down": [{"func": self._handle_down}], + "up": [{"func": self._handle_up}], + "left": [{"func": self._handle_left}], + "right": [{"func": self._handle_right}], + } + + self._layout = FloatContainer( + content=HSplit( + [ + VSplit( + [ + Window( + height=LayoutDimension.exact(1) + if not self._wrap_lines + else None, + content=FormattedTextControl(self._message), + wrap_lines=self._wrap_lines, + dont_extend_height=True, + ) + ] + ) + ] + ), + floats=[ + Float( + content=ValidationWindow( + invalid_message=self._invalid_message, + filter=self._is_invalid & ~IsDone(), + wrap_lines=self._wrap_lines, + ), + left=0, + bottom=self._validation_window_bottom_offset, + ) + ], + ) + + def _handle_down(self) -> None: + pass + + def _handle_up(self) -> None: + pass + + def _handle_left(self) -> None: + pass + + def _handle_right(self) -> None: + pass diff --git a/examples/alternate/number.py b/examples/alternate/number.py new file mode 100644 index 0000000..d51db85 --- /dev/null +++ b/examples/alternate/number.py @@ -0,0 +1,3 @@ +from InquirerPy import inquirer + +# result = inquirer. From c918cb0c1f21718af9a722abdad2042c63a0ae9c Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Thu, 18 Nov 2021 10:12:07 +1100 Subject: [PATCH 02/41] feat(number): added input buffers --- InquirerPy/prompts/number.py | 145 +++++++++++++++++++++++++++-------- examples/alternate/number.py | 6 +- 2 files changed, 120 insertions(+), 31 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index f36184c..b6ee499 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -1,18 +1,27 @@ """Module contains the class to create a number prompt.""" -from typing import Any, Callable, Union, cast +from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast +from prompt_toolkit.application.application import Application +from prompt_toolkit.buffer import Buffer from prompt_toolkit.filters.cli import IsDone from prompt_toolkit.layout.containers import ( - Float, - FloatContainer, + ConditionalContainer, + HorizontalAlign, HSplit, VSplit, Window, ) -from prompt_toolkit.layout.controls import FormattedTextControl -from prompt_toolkit.layout.dimension import LayoutDimension +from prompt_toolkit.layout.controls import ( + BufferControl, + DummyControl, + FormattedTextControl, +) +from prompt_toolkit.layout.dimension import Dimension, LayoutDimension +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.lexers.base import SimpleLexer from InquirerPy.base.complex import BaseComplexPrompt +from InquirerPy.containers.instruction import InstructionWindow from InquirerPy.containers.validation import ValidationWindow from InquirerPy.enum import INQUIRERPY_QMARK_SEQUENCE from InquirerPy.exceptions import InvalidArgument @@ -25,6 +34,9 @@ InquirerPyValidate, ) +if TYPE_CHECKING: + from prompt_toolkit.key_binding.key_processor import KeyPressEvent + __all__ = ["NumberPrompt"] @@ -36,7 +48,7 @@ def __init__( message: InquirerPyMessage, style: InquirerPyStyle = None, vi_mode: bool = False, - default: InquirerPyDefault = "", + default: InquirerPyDefault = 0, float_allowed: bool = False, max_allowed: Union[int, float] = None, min_allowed: Union[int, float] = None, @@ -61,12 +73,12 @@ def __init__( vi_mode=vi_mode, qmark=qmark, amark=amark, - instruction=instruction, - long_instruction=long_instruction, - validate=validate, - invalid_message=invalid_message, transformer=transformer, filter=filter, + invalid_message=invalid_message, + validate=validate, + instruction=instruction, + long_instruction=long_instruction, wrap_lines=wrap_lines, raise_keyboard_interrupt=raise_keyboard_interrupt, mandatory=mandatory, @@ -113,8 +125,41 @@ def __init__( "right": [{"func": self._handle_right}], } - self._layout = FloatContainer( - content=HSplit( + self._whole_width = 1 + self._whole_buffer = Buffer(on_text_changed=self._on_whole_text_change) + + self._integral_width = 1 + self._integral_buffer = Buffer(on_text_changed=self._on_integral_text_change) + + self._whole_window = Window( + height=LayoutDimension.exact(1) if not self._wrap_lines else None, + content=BufferControl( + buffer=self._whole_buffer, + lexer=SimpleLexer("class:input"), + ), + width=lambda: Dimension( + min=self._whole_width, + max=self._whole_width, + preferred=self._whole_width, + ), + dont_extend_width=True, + ) + + self._integral_window = Window( + height=LayoutDimension.exact(1) if not self._wrap_lines else None, + content=BufferControl( + buffer=self._integral_buffer, + lexer=SimpleLexer("class:input"), + ), + width=lambda: Dimension( + min=self._integral_width, + max=self._integral_width, + preferred=self._integral_width, + ), + ) + + self._layout = Layout( + HSplit( [ VSplit( [ @@ -122,35 +167,75 @@ def __init__( height=LayoutDimension.exact(1) if not self._wrap_lines else None, - content=FormattedTextControl(self._message), + content=FormattedTextControl(self._get_prompt_message), wrap_lines=self._wrap_lines, dont_extend_height=True, - ) - ] - ) - ] - ), - floats=[ - Float( - content=ValidationWindow( - invalid_message=self._invalid_message, + dont_extend_width=True, + ), + self._whole_window, + Window( + height=LayoutDimension.exact(1) + if not self._wrap_lines + else None, + content=FormattedTextControl([("", ". ")]), + wrap_lines=self._wrap_lines, + dont_extend_height=True, + dont_extend_width=True, + ), + self._integral_window, + ], + align=HorizontalAlign.LEFT, + ), + ConditionalContainer( + Window(content=DummyControl()), + filter=~IsDone() & self._is_displaying_long_instruction, + ), + ValidationWindow( + invalid_message=self._get_error_message, filter=self._is_invalid & ~IsDone(), wrap_lines=self._wrap_lines, ), - left=0, - bottom=self._validation_window_bottom_offset, - ) - ], + InstructionWindow( + message=self._long_instruction, + filter=~IsDone() & self._is_displaying_long_instruction, + wrap_lines=self._wrap_lines, + ), + ] + ), + ) + + self._layout.focus(self._integral_window) + + self._application = Application( + layout=self._layout, + style=self._style, + key_bindings=self._kb, + after_render=self._after_render, ) - def _handle_down(self) -> None: + def _on_rendered(self, _) -> None: + self._whole_buffer.text = "0" + self._whole_buffer.cursor_position = 1 + self._integral_buffer.text = "0" + self._integral_buffer.cursor_position = 1 + + def _handle_down(self, event: Optional["KeyPressEvent"]) -> None: + pass + + def _handle_up(self, event: Optional["KeyPressEvent"]) -> None: pass - def _handle_up(self) -> None: + def _handle_left(self, event: Optional["KeyPressEvent"]) -> None: pass - def _handle_left(self) -> None: + def _handle_right(self, event: Optional["KeyPressEvent"]) -> None: pass - def _handle_right(self) -> None: + def _handle_enter(self, event: Optional["KeyPressEvent"]) -> None: pass + + def _on_whole_text_change(self, buffer: Buffer) -> None: + self._whole_width = len(buffer.text) + 1 + + def _on_integral_text_change(self, buffer: Buffer) -> None: + self._integral_width = len(buffer.text) + 1 diff --git a/examples/alternate/number.py b/examples/alternate/number.py index d51db85..81738a7 100644 --- a/examples/alternate/number.py +++ b/examples/alternate/number.py @@ -1,3 +1,7 @@ from InquirerPy import inquirer -# result = inquirer. +result = inquirer.number( + message="hello", + long_instruction="asfasdfasdfa asdfas", + raise_keyboard_interrupt=False, +).execute() From edc6f1682937d6ef2d91592b9644ce4a55ae81ad Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Fri, 19 Nov 2021 17:03:50 +1100 Subject: [PATCH 03/41] feat: allow movement --- InquirerPy/prompts/number.py | 54 ++++++++++++++++++++++++++++++------ examples/alternate/number.py | 1 + 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index b6ee499..ae5923f 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -4,6 +4,7 @@ from prompt_toolkit.application.application import Application from prompt_toolkit.buffer import Buffer from prompt_toolkit.filters.cli import IsDone +from prompt_toolkit.keys import Keys from prompt_toolkit.layout.containers import ( ConditionalContainer, HorizontalAlign, @@ -92,10 +93,13 @@ def __init__( if isinstance(default, Callable): default = cast(Callable, default)(session_result) - if self._float and not isinstance(default, float): - raise InvalidArgument( - f"{type(self).__name__} argument 'default' should return type of float" - ) + if self._float: + default = float(cast(int, default)) + if self._float: + if not isinstance(default, float): + raise InvalidArgument( + f"{type(self).__name__} argument 'default' should return type of float" + ) elif not isinstance(default, int): raise InvalidArgument( f"{type(self).__name__} argument 'default' should return type of int" @@ -116,6 +120,7 @@ def __init__( ], "left": [{"key": "left"}], "right": [{"key": "right"}], + "focus": [{"key": Keys.Tab}, {"key": "s-tab"}], **keybindings, } self.kb_func_lookup = { @@ -123,6 +128,7 @@ def __init__( "up": [{"func": self._handle_up}], "left": [{"func": self._handle_left}], "right": [{"func": self._handle_right}], + "focus": [{"func": self._handle_focus}], } self._whole_width = 1 @@ -204,7 +210,7 @@ def __init__( ), ) - self._layout.focus(self._integral_window) + self.focus = self._whole_window self._application = Application( layout=self._layout, @@ -215,9 +221,9 @@ def __init__( def _on_rendered(self, _) -> None: self._whole_buffer.text = "0" - self._whole_buffer.cursor_position = 1 + self._whole_buffer.cursor_position = 0 self._integral_buffer.text = "0" - self._integral_buffer.cursor_position = 1 + self._integral_buffer.cursor_position = 0 def _handle_down(self, event: Optional["KeyPressEvent"]) -> None: pass @@ -226,14 +232,44 @@ def _handle_up(self, event: Optional["KeyPressEvent"]) -> None: pass def _handle_left(self, event: Optional["KeyPressEvent"]) -> None: - pass + if self.focus == self._whole_window: + self._whole_buffer.cursor_position -= 1 + else: + if self._integral_buffer.cursor_position == 0: + self.focus = self._whole_window + else: + self._integral_buffer.cursor_position -= 1 def _handle_right(self, event: Optional["KeyPressEvent"]) -> None: - pass + if self.focus == self._integral_window: + self._integral_buffer.cursor_position += 1 + else: + if self._whole_buffer.cursor_position == len(self._whole_buffer.text): + self.focus = self._integral_window + else: + self._whole_buffer.cursor_position += 1 def _handle_enter(self, event: Optional["KeyPressEvent"]) -> None: pass + def _handle_focus(self, event: Optional["KeyPressEvent"]) -> None: + if not self._float: + return + if self.focus == self._whole_window: + self.focus = self._integral_window + else: + self.focus = self._whole_window + + @property + def focus(self) -> Window: + """Window: Current focused window.""" + return self._focus + + @focus.setter + def focus(self, value: Window) -> None: + self._focus = value + self._layout.focus(self._focus) + def _on_whole_text_change(self, buffer: Buffer) -> None: self._whole_width = len(buffer.text) + 1 diff --git a/examples/alternate/number.py b/examples/alternate/number.py index 81738a7..0183fb5 100644 --- a/examples/alternate/number.py +++ b/examples/alternate/number.py @@ -4,4 +4,5 @@ message="hello", long_instruction="asfasdfasdfa asdfas", raise_keyboard_interrupt=False, + float_allowed=True, ).execute() From c12daed9af93bf126ca488455fbde2a30aa0eec5 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 23 Nov 2021 08:41:07 +1100 Subject: [PATCH 04/41] feat: hide integral window if not float --- InquirerPy/prompts/number.py | 66 +++++++++++++++++++++++------------- examples/alternate/number.py | 2 +- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index ae5923f..b6e1fc3 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -3,6 +3,7 @@ from prompt_toolkit.application.application import Application from prompt_toolkit.buffer import Buffer +from prompt_toolkit.filters.base import Condition from prompt_toolkit.filters.cli import IsDone from prompt_toolkit.keys import Keys from prompt_toolkit.layout.containers import ( @@ -37,6 +38,7 @@ if TYPE_CHECKING: from prompt_toolkit.key_binding.key_processor import KeyPressEvent + from prompt_toolkit.layout.containers import Container __all__ = ["NumberPrompt"] @@ -88,6 +90,7 @@ def __init__( ) self._float = float_allowed + self._is_float = Condition(lambda: self._float) self._max = max_allowed self._min = min_allowed @@ -118,8 +121,16 @@ def __init__( {"key": "c-p", "filter": ~self._is_vim_edit}, {"key": "k", "filter": self._is_vim_edit}, ], - "left": [{"key": "left"}], - "right": [{"key": "right"}], + "left": [ + {"key": "left"}, + {"key": "c-b", "filter": ~self._is_vim_edit}, + {"key": "h", "filter": self._is_vim_edit}, + ], + "right": [ + {"key": "right"}, + {"key": "c-f", "filter": ~self._is_vim_edit}, + {"key": "l", "filter": self._is_vim_edit}, + ], "focus": [{"key": Keys.Tab}, {"key": "s-tab"}], **keybindings, } @@ -151,17 +162,20 @@ def __init__( dont_extend_width=True, ) - self._integral_window = Window( - height=LayoutDimension.exact(1) if not self._wrap_lines else None, - content=BufferControl( - buffer=self._integral_buffer, - lexer=SimpleLexer("class:input"), - ), - width=lambda: Dimension( - min=self._integral_width, - max=self._integral_width, - preferred=self._integral_width, + self._integral_window = ConditionalContainer( + content=Window( + height=LayoutDimension.exact(1) if not self._wrap_lines else None, + content=BufferControl( + buffer=self._integral_buffer, + lexer=SimpleLexer("class:input"), + ), + width=lambda: Dimension( + min=self._integral_width, + max=self._integral_width, + preferred=self._integral_width, + ), ), + filter=self._is_float, ) self._layout = Layout( @@ -179,14 +193,17 @@ def __init__( dont_extend_width=True, ), self._whole_window, - Window( - height=LayoutDimension.exact(1) - if not self._wrap_lines - else None, - content=FormattedTextControl([("", ". ")]), - wrap_lines=self._wrap_lines, - dont_extend_height=True, - dont_extend_width=True, + ConditionalContainer( + Window( + height=LayoutDimension.exact(1) + if not self._wrap_lines + else None, + content=FormattedTextControl([("", ". ")]), + wrap_lines=self._wrap_lines, + dont_extend_height=True, + dont_extend_width=True, + ), + filter=self._is_float, ), self._integral_window, ], @@ -244,7 +261,10 @@ def _handle_right(self, event: Optional["KeyPressEvent"]) -> None: if self.focus == self._integral_window: self._integral_buffer.cursor_position += 1 else: - if self._whole_buffer.cursor_position == len(self._whole_buffer.text): + if ( + self._whole_buffer.cursor_position == len(self._whole_buffer.text) + and self._float + ): self.focus = self._integral_window else: self._whole_buffer.cursor_position += 1 @@ -261,12 +281,12 @@ def _handle_focus(self, event: Optional["KeyPressEvent"]) -> None: self.focus = self._whole_window @property - def focus(self) -> Window: + def focus(self) -> "Container": """Window: Current focused window.""" return self._focus @focus.setter - def focus(self, value: Window) -> None: + def focus(self, value: "Container") -> None: self._focus = value self._layout.focus(self._focus) diff --git a/examples/alternate/number.py b/examples/alternate/number.py index 0183fb5..9068a61 100644 --- a/examples/alternate/number.py +++ b/examples/alternate/number.py @@ -4,5 +4,5 @@ message="hello", long_instruction="asfasdfasdfa asdfas", raise_keyboard_interrupt=False, - float_allowed=True, + # float_allowed=True, ).execute() From f4103b94483e634a22e00b98615cc425cd8148e1 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 23 Nov 2021 08:54:27 +1100 Subject: [PATCH 05/41] feat(number): handle number input --- InquirerPy/prompts/number.py | 45 ++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index b6e1fc3..6dd758c 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -38,7 +38,6 @@ if TYPE_CHECKING: from prompt_toolkit.key_binding.key_processor import KeyPressEvent - from prompt_toolkit.layout.containers import Container __all__ = ["NumberPrompt"] @@ -132,6 +131,7 @@ def __init__( {"key": "l", "filter": self._is_vim_edit}, ], "focus": [{"key": Keys.Tab}, {"key": "s-tab"}], + "input": [{"key": str(i)} for i in range(10)], **keybindings, } self.kb_func_lookup = { @@ -140,8 +140,13 @@ def __init__( "left": [{"func": self._handle_left}], "right": [{"func": self._handle_right}], "focus": [{"func": self._handle_focus}], + "input": [{"func": self._handle_input}], } + @self.register_kb(Keys.Any) + def _(_): + pass + self._whole_width = 1 self._whole_buffer = Buffer(on_text_changed=self._on_whole_text_change) @@ -162,20 +167,17 @@ def __init__( dont_extend_width=True, ) - self._integral_window = ConditionalContainer( - content=Window( - height=LayoutDimension.exact(1) if not self._wrap_lines else None, - content=BufferControl( - buffer=self._integral_buffer, - lexer=SimpleLexer("class:input"), - ), - width=lambda: Dimension( - min=self._integral_width, - max=self._integral_width, - preferred=self._integral_width, - ), + self._integral_window = Window( + height=LayoutDimension.exact(1) if not self._wrap_lines else None, + content=BufferControl( + buffer=self._integral_buffer, + lexer=SimpleLexer("class:input"), + ), + width=lambda: Dimension( + min=self._integral_width, + max=self._integral_width, + preferred=self._integral_width, ), - filter=self._is_float, ) self._layout = Layout( @@ -205,7 +207,9 @@ def __init__( ), filter=self._is_float, ), - self._integral_window, + ConditionalContainer( + self._integral_window, filter=self._is_float + ), ], align=HorizontalAlign.LEFT, ), @@ -280,13 +284,20 @@ def _handle_focus(self, event: Optional["KeyPressEvent"]) -> None: else: self.focus = self._whole_window + def _handle_input(self, event: "KeyPressEvent") -> None: + data = event.key_sequence[0].data + if self.focus == self._whole_window: + self._whole_buffer.insert_text(data) + else: + self._integral_buffer.insert_text(data) + @property - def focus(self) -> "Container": + def focus(self) -> Window: """Window: Current focused window.""" return self._focus @focus.setter - def focus(self, value: "Container") -> None: + def focus(self, value: Window) -> None: self._focus = value self._layout.focus(self._focus) From d2df65c6e5e70abb631e20e3e78cf747fa3c716c Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 23 Nov 2021 09:06:02 +1100 Subject: [PATCH 06/41] refactor(number): move buffer detection logic --- InquirerPy/prompts/number.py | 46 +++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 6dd758c..5d45f50 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -252,31 +252,29 @@ def _handle_down(self, event: Optional["KeyPressEvent"]) -> None: def _handle_up(self, event: Optional["KeyPressEvent"]) -> None: pass - def _handle_left(self, event: Optional["KeyPressEvent"]) -> None: - if self.focus == self._whole_window: - self._whole_buffer.cursor_position -= 1 + def _handle_left(self, _) -> None: + if ( + self.focus == self._integral_window + and self.focus_buffer.cursor_position == 0 + ): + self.focus = self._whole_window else: - if self._integral_buffer.cursor_position == 0: - self.focus = self._whole_window - else: - self._integral_buffer.cursor_position -= 1 + self.focus_buffer.cursor_position -= 1 - def _handle_right(self, event: Optional["KeyPressEvent"]) -> None: - if self.focus == self._integral_window: - self._integral_buffer.cursor_position += 1 + def _handle_right(self, _) -> None: + if ( + self.focus == self._whole_window + and self.focus_buffer.cursor_position == len(self.focus_buffer.text) + and self._float + ): + self.focus = self._integral_window else: - if ( - self._whole_buffer.cursor_position == len(self._whole_buffer.text) - and self._float - ): - self.focus = self._integral_window - else: - self._whole_buffer.cursor_position += 1 + self.focus_buffer.cursor_position += 1 - def _handle_enter(self, event: Optional["KeyPressEvent"]) -> None: + def _handle_enter(self, _) -> None: pass - def _handle_focus(self, event: Optional["KeyPressEvent"]) -> None: + def _handle_focus(self, _) -> None: if not self._float: return if self.focus == self._whole_window: @@ -285,11 +283,15 @@ def _handle_focus(self, event: Optional["KeyPressEvent"]) -> None: self.focus = self._whole_window def _handle_input(self, event: "KeyPressEvent") -> None: - data = event.key_sequence[0].data + self.focus_buffer.insert_text(event.key_sequence[0].data) + + @property + def focus_buffer(self) -> Buffer: + """Buffer: Current editable buffer.""" if self.focus == self._whole_window: - self._whole_buffer.insert_text(data) + return self._whole_buffer else: - self._integral_buffer.insert_text(data) + return self._integral_buffer @property def focus(self) -> Window: From c7216bc3208ab924b3d55f064218c3cca878408f Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 23 Nov 2021 09:39:17 +1100 Subject: [PATCH 07/41] feat(number): toggle negative --- InquirerPy/prompts/number.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 5d45f50..df96864 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -132,6 +132,7 @@ def __init__( ], "focus": [{"key": Keys.Tab}, {"key": "s-tab"}], "input": [{"key": str(i)} for i in range(10)], + "negative_toggle": [{"key": "-"}], **keybindings, } self.kb_func_lookup = { @@ -141,6 +142,7 @@ def __init__( "right": [{"func": self._handle_right}], "focus": [{"func": self._handle_focus}], "input": [{"func": self._handle_input}], + "negative_toggle": [{"func": self._handle_negative_toggle}], } @self.register_kb(Keys.Any) @@ -285,6 +287,12 @@ def _handle_focus(self, _) -> None: def _handle_input(self, event: "KeyPressEvent") -> None: self.focus_buffer.insert_text(event.key_sequence[0].data) + def _handle_negative_toggle(self, _) -> None: + if self._whole_buffer.text.startswith("-"): + self._whole_buffer.text = self._whole_buffer.text[1:] + else: + self._whole_buffer.text = f"-{self._whole_buffer.text}" + @property def focus_buffer(self) -> Buffer: """Buffer: Current editable buffer.""" From 32a77b275174881d8fde1c46072bbde92d075662 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 23 Nov 2021 10:34:41 +1100 Subject: [PATCH 08/41] feat(number): handle increase and decrease --- InquirerPy/prompts/number.py | 81 ++++++++++++++++++++++++++++++------ examples/alternate/number.py | 2 + 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index df96864..5babbed 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -1,5 +1,5 @@ """Module contains the class to create a number prompt.""" -from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast +from typing import TYPE_CHECKING, Any, Callable, Union, cast from prompt_toolkit.application.application import Application from prompt_toolkit.buffer import Buffer @@ -92,6 +92,7 @@ def __init__( self._is_float = Condition(lambda: self._float) self._max = max_allowed self._min = min_allowed + self._value_error_message = "Remove any non-integer value" if isinstance(default, Callable): default = cast(Callable, default)(session_result) @@ -106,6 +107,7 @@ def __init__( raise InvalidArgument( f"{type(self).__name__} argument 'default' should return type of int" ) + self._default = default if keybindings is None: keybindings = {} @@ -150,10 +152,16 @@ def _(_): pass self._whole_width = 1 - self._whole_buffer = Buffer(on_text_changed=self._on_whole_text_change) + self._whole_buffer = Buffer( + on_text_changed=self._on_whole_text_change, + on_cursor_position_changed=self._on_cursor_position_change, + ) self._integral_width = 1 - self._integral_buffer = Buffer(on_text_changed=self._on_integral_text_change) + self._integral_buffer = Buffer( + on_text_changed=self._on_integral_text_change, + on_cursor_position_changed=self._on_cursor_position_change, + ) self._whole_window = Window( height=LayoutDimension.exact(1) if not self._wrap_lines else None, @@ -248,11 +256,23 @@ def _on_rendered(self, _) -> None: self._integral_buffer.text = "0" self._integral_buffer.cursor_position = 0 - def _handle_down(self, event: Optional["KeyPressEvent"]) -> None: - pass - - def _handle_up(self, event: Optional["KeyPressEvent"]) -> None: - pass + def _handle_down(self, _) -> None: + try: + if not self.focus_buffer.text: + self.focus_buffer.text = "0" + else: + self.focus_buffer.text = str(int(self.focus_buffer.text) - 1) + except ValueError: + self._set_error(message=self._value_error_message) + + def _handle_up(self, _) -> None: + try: + if not self.focus_buffer.text: + self.focus_buffer.text = "0" + else: + self.focus_buffer.text = str(int(self.focus_buffer.text) + 1) + except ValueError: + self._set_error(message=self._value_error_message) def _handle_left(self, _) -> None: if ( @@ -293,6 +313,24 @@ def _handle_negative_toggle(self, _) -> None: else: self._whole_buffer.text = f"-{self._whole_buffer.text}" + def _on_whole_text_change(self, buffer: Buffer) -> None: + self._whole_width = len(buffer.text) + 1 + self._on_text_change(buffer) + + def _on_integral_text_change(self, buffer: Buffer) -> None: + self._integral_width = len(buffer.text) + 1 + self._on_text_change(buffer) + + def _on_text_change(self, buffer: Buffer) -> None: + if buffer.text and buffer.text != "-": + self.value = self.value + if buffer.text.startswith("-") and buffer.cursor_position == 0: + buffer.cursor_position = 1 + + def _on_cursor_position_change(self, buffer: Buffer) -> None: + if self.focus_buffer.text.startswith("-") and buffer.cursor_position == 0: + buffer.cursor_position = 1 + @property def focus_buffer(self) -> Buffer: """Buffer: Current editable buffer.""" @@ -311,8 +349,25 @@ def focus(self, value: Window) -> None: self._focus = value self._layout.focus(self._focus) - def _on_whole_text_change(self, buffer: Buffer) -> None: - self._whole_width = len(buffer.text) + 1 - - def _on_integral_text_change(self, buffer: Buffer) -> None: - self._integral_width = len(buffer.text) + 1 + @property + def value(self) -> Union[int, float]: + """Union[int, float]: The actual value of the prompt, combining and transforming all input buffer values.""" + try: + if not self._float: + return int(self._whole_buffer.text) + else: + return float(f"{self._whole_buffer.text}.{self._integral_buffer.text}") + except ValueError: + self._set_error(self._value_error_message) + return self._default + + @value.setter + def value(self, value: Union[int, float]) -> None: + if self._min is not None: + value = max(value, self._min) + if self._max is not None: + value = min(value, self._max) + if not self._float: + self._whole_buffer.text = str(value) + else: + self._whole_buffer.text, self._integral_buffer.text = str(value).split(".") diff --git a/examples/alternate/number.py b/examples/alternate/number.py index 9068a61..142ae22 100644 --- a/examples/alternate/number.py +++ b/examples/alternate/number.py @@ -4,5 +4,7 @@ message="hello", long_instruction="asfasdfasdfa asdfas", raise_keyboard_interrupt=False, + min_allowed=-10, + max_allowed=10 # float_allowed=True, ).execute() From b0bde419922d9cb4aee2c1952c8723c70d17ee9a Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 30 Nov 2021 09:05:14 +1100 Subject: [PATCH 09/41] feat(number): allow customisation on decimal symbol --- InquirerPy/prompts/number.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 5babbed..5d58517 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -54,6 +54,7 @@ def __init__( float_allowed: bool = False, max_allowed: Union[int, float] = None, min_allowed: Union[int, float] = None, + decimal_symbol: str = ". ", qmark: str = INQUIRERPY_QMARK_SEQUENCE, amark: str = "?", instruction: str = "", @@ -93,6 +94,7 @@ def __init__( self._max = max_allowed self._min = min_allowed self._value_error_message = "Remove any non-integer value" + self._decimal_symbol = decimal_symbol if isinstance(default, Callable): default = cast(Callable, default)(session_result) @@ -210,7 +212,9 @@ def _(_): height=LayoutDimension.exact(1) if not self._wrap_lines else None, - content=FormattedTextControl([("", ". ")]), + content=FormattedTextControl( + [("", self._decimal_symbol)] + ), wrap_lines=self._wrap_lines, dont_extend_height=True, dont_extend_width=True, From 3670630f44df38fa3f3133e01f4639b68aa2bc25 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 30 Nov 2021 09:05:46 +1100 Subject: [PATCH 10/41] fix(number): exception if max/min is set for float --- InquirerPy/prompts/number.py | 4 ++-- examples/alternate/number.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 5d58517..b2c94e0 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -368,9 +368,9 @@ def value(self) -> Union[int, float]: @value.setter def value(self, value: Union[int, float]) -> None: if self._min is not None: - value = max(value, self._min) + value = max(value, self._min if not self._float else float(self._min)) if self._max is not None: - value = min(value, self._max) + value = min(value, self._max if not self._float else float(self._max)) if not self._float: self._whole_buffer.text = str(value) else: diff --git a/examples/alternate/number.py b/examples/alternate/number.py index 142ae22..503e6f5 100644 --- a/examples/alternate/number.py +++ b/examples/alternate/number.py @@ -5,6 +5,6 @@ long_instruction="asfasdfasdfa asdfas", raise_keyboard_interrupt=False, min_allowed=-10, - max_allowed=10 - # float_allowed=True, + max_allowed=10, + float_allowed=True, ).execute() From 9d09a0f30c96a3e56d7c02f2148c0ad5600a970c Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 30 Nov 2021 09:31:55 +1100 Subject: [PATCH 11/41] feat(number): allow zero decimal input --- InquirerPy/prompts/number.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index b2c94e0..6c58400 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -95,6 +95,7 @@ def __init__( self._min = min_allowed self._value_error_message = "Remove any non-integer value" self._decimal_symbol = decimal_symbol + self._ending_zero = False if isinstance(default, Callable): default = cast(Callable, default)(session_result) @@ -360,6 +361,11 @@ def value(self) -> Union[int, float]: if not self._float: return int(self._whole_buffer.text) else: + self._ending_zero = ( + self._integral_buffer.text.endswith("0") + if len(self._integral_buffer.text) > 1 + else False + ) return float(f"{self._whole_buffer.text}.{self._integral_buffer.text}") except ValueError: self._set_error(self._value_error_message) @@ -374,4 +380,8 @@ def value(self, value: Union[int, float]) -> None: if not self._float: self._whole_buffer.text = str(value) else: - self._whole_buffer.text, self._integral_buffer.text = str(value).split(".") + self._whole_buffer.text, integral_buffer_text = str(value).split(".") + if self._ending_zero: + self._integral_buffer.text = integral_buffer_text + "0" + else: + self._integral_buffer.text = integral_buffer_text From 33d622de0a5a54d256d36fd46abaf2f29082fd86 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 30 Nov 2021 09:42:34 +1100 Subject: [PATCH 12/41] fix(number): negative decimal --- InquirerPy/prompts/number.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 6c58400..217623a 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -266,6 +266,11 @@ def _handle_down(self, _) -> None: if not self.focus_buffer.text: self.focus_buffer.text = "0" else: + if ( + self.focus_buffer == self._integral_buffer + and int(self.focus_buffer.text) == 0 + ): + return self.focus_buffer.text = str(int(self.focus_buffer.text) - 1) except ValueError: self._set_error(message=self._value_error_message) From 616168df855d7e7e1ca4c9cbb16c7edcb8547238 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Wed, 1 Dec 2021 09:45:32 +1100 Subject: [PATCH 13/41] chore: udpate dev dependencies --- poetry.lock | 137 ++++++++++++++++++++++++++-------------------------- 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/poetry.lock b/poetry.lock index 26bdc77..20bb5e2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -63,7 +63,7 @@ lxml = ["lxml"] [[package]] name = "black" -version = "21.11b0" +version = "21.11b1" description = "The uncompromising code formatter." category = "dev" optional = false @@ -74,7 +74,7 @@ click = ">=7.1.2" mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0,<1" platformdirs = ">=2" -regex = ">=2020.1.8" +regex = ">=2021.4.4" tomli = ">=0.2.6,<2.0.0" typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = [ @@ -107,7 +107,7 @@ python-versions = ">=3.6.1" [[package]] name = "charset-normalizer" -version = "2.0.7" +version = "2.0.8" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -138,7 +138,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.1.2" +version = "6.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -201,7 +201,7 @@ testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-co [[package]] name = "furo" -version = "2021.11.16" +version = "2021.11.23" description = "A clean customisable Sphinx documentation theme." category = "main" optional = true @@ -209,6 +209,7 @@ python-versions = ">=3.6" [package.dependencies] beautifulsoup4 = "*" +pygments = ">=2.7,<3.0" sphinx = ">=4.0,<5.0" [package.extras] @@ -217,14 +218,14 @@ doc = ["myst-parser", "sphinx-copybutton", "sphinx-design", "sphinx-inline-tabs" [[package]] name = "identify" -version = "2.3.6" +version = "2.4.0" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.6.1" [package.extras] -license = ["editdistance-s"] +license = ["ukkonen"] [[package]] name = "idna" @@ -442,7 +443,7 @@ virtualenv = ">=20.0.8" [[package]] name = "prompt-toolkit" -version = "3.0.22" +version = "3.0.23" description = "Library for building powerful interactive command lines in Python" category = "main" optional = false @@ -552,7 +553,7 @@ python-versions = ">=3.6" [[package]] name = "sphinx" -version = "4.3.0" +version = "4.3.1" description = "Python documentation generator" category = "main" optional = true @@ -806,8 +807,8 @@ beautifulsoup4 = [ {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"}, ] black = [ - {file = "black-21.11b0-py3-none-any.whl", hash = "sha256:0b1f66cbfadcd332ceeaeecf6373d9991d451868d2e2219ad0ac1213fb701117"}, - {file = "black-21.11b0.tar.gz", hash = "sha256:83f3852301c8dcb229e9c444dd79f573c8d31c7c2dad9bbaaa94c808630e32aa"}, + {file = "black-21.11b1-py3-none-any.whl", hash = "sha256:802c6c30b637b28645b7fde282ed2569c0cd777dbe493a41b6a03c1d903f99ac"}, + {file = "black-21.11b1.tar.gz", hash = "sha256:a042adbb18b3262faad5aff4e834ff186bb893f95ba3a8013f09de1e5569def2"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, @@ -818,8 +819,8 @@ cfgv = [ {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, - {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, + {file = "charset-normalizer-2.0.8.tar.gz", hash = "sha256:735e240d9a8506778cd7a453d97e817e536bb1fc29f4f6961ce297b9c7a917b0"}, + {file = "charset_normalizer-2.0.8-py3-none-any.whl", hash = "sha256:83fcdeb225499d6344c8f7f34684c2981270beacc32ede2e669e94f7fa544405"}, ] click = [ {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, @@ -830,53 +831,53 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-6.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:675adb3b3380967806b3cbb9c5b00ceb29b1c472692100a338730c1d3e59c8b9"}, - {file = "coverage-6.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95a58336aa111af54baa451c33266a8774780242cab3704b7698d5e514840758"}, - {file = "coverage-6.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d0a595a781f8e186580ff8e3352dd4953b1944289bec7705377c80c7e36c4d6c"}, - {file = "coverage-6.1.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d3c5f49ce6af61154060640ad3b3281dbc46e2e0ef2fe78414d7f8a324f0b649"}, - {file = "coverage-6.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:310c40bed6b626fd1f463e5a83dba19a61c4eb74e1ac0d07d454ebbdf9047e9d"}, - {file = "coverage-6.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a4d48e42e17d3de212f9af44f81ab73b9378a4b2b8413fd708d0d9023f2bbde4"}, - {file = "coverage-6.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ffa545230ca2ad921ad066bf8fd627e7be43716b6e0fcf8e32af1b8188ccb0ab"}, - {file = "coverage-6.1.2-cp310-cp310-win32.whl", hash = "sha256:cd2d11a59afa5001ff28073ceca24ae4c506da4355aba30d1e7dd2bd0d2206dc"}, - {file = "coverage-6.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:96129e41405887a53a9cc564f960d7f853cc63d178f3a182fdd302e4cab2745b"}, - {file = "coverage-6.1.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1de9c6f5039ee2b1860b7bad2c7bc3651fbeb9368e4c4d93e98a76358cdcb052"}, - {file = "coverage-6.1.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:80cb70264e9a1d04b519cdba3cd0dc42847bf8e982a4d55c769b9b0ee7cdce1e"}, - {file = "coverage-6.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:ba6125d4e55c0b8e913dad27b22722eac7abdcb1f3eab1bd090eee9105660266"}, - {file = "coverage-6.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8492d37acdc07a6eac6489f6c1954026f2260a85a4c2bb1e343fe3d35f5ee21a"}, - {file = "coverage-6.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66af99c7f7b64d050d37e795baadf515b4561124f25aae6e1baa482438ecc388"}, - {file = "coverage-6.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ebcc03e1acef4ff44f37f3c61df478d6e469a573aa688e5a162f85d7e4c3860d"}, - {file = "coverage-6.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98d44a8136eebbf544ad91fef5bd2b20ef0c9b459c65a833c923d9aa4546b204"}, - {file = "coverage-6.1.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c18725f3cffe96732ef96f3de1939d81215fd6d7d64900dcc4acfe514ea4fcbf"}, - {file = "coverage-6.1.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c8e9c4bcaaaa932be581b3d8b88b677489975f845f7714efc8cce77568b6711c"}, - {file = "coverage-6.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:06d009e8a29483cbc0520665bc46035ffe9ae0e7484a49f9782c2a716e37d0a0"}, - {file = "coverage-6.1.2-cp36-cp36m-win32.whl", hash = "sha256:e5432d9c329b11c27be45ee5f62cf20a33065d482c8dec1941d6670622a6fb8f"}, - {file = "coverage-6.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:82fdcb64bf08aa5db881db061d96db102c77397a570fbc112e21c48a4d9cb31b"}, - {file = "coverage-6.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:94f558f8555e79c48c422045f252ef41eb43becdd945e9c775b45ebfc0cbd78f"}, - {file = "coverage-6.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:046647b96969fda1ae0605f61288635209dd69dcd27ba3ec0bf5148bc157f954"}, - {file = "coverage-6.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cc799916b618ec9fd00135e576424165691fec4f70d7dc12cfaef09268a2478c"}, - {file = "coverage-6.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62646d98cf0381ffda301a816d6ac6c35fc97aa81b09c4c52d66a15c4bef9d7c"}, - {file = "coverage-6.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:27a3df08a855522dfef8b8635f58bab81341b2fb5f447819bc252da3aa4cf44c"}, - {file = "coverage-6.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:610c0ba11da8de3a753dc4b1f71894f9f9debfdde6559599f303286e70aeb0c2"}, - {file = "coverage-6.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:35b246ae3a2c042dc8f410c94bcb9754b18179cdb81ff9477a9089dbc9ecc186"}, - {file = "coverage-6.1.2-cp37-cp37m-win32.whl", hash = "sha256:0cde7d9fe2fb55ff68ebe7fb319ef188e9b88e0a3d1c9c5db7dd829cd93d2193"}, - {file = "coverage-6.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:958ac66272ff20e63d818627216e3d7412fdf68a2d25787b89a5c6f1eb7fdd93"}, - {file = "coverage-6.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a300b39c3d5905686c75a369d2a66e68fd01472ea42e16b38c948bd02b29e5bd"}, - {file = "coverage-6.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d3855d5d26292539861f5ced2ed042fc2aa33a12f80e487053aed3bcb6ced13"}, - {file = "coverage-6.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:586d38dfc7da4a87f5816b203ff06dd7c1bb5b16211ccaa0e9788a8da2b93696"}, - {file = "coverage-6.1.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a34fccb45f7b2d890183a263578d60a392a1a218fdc12f5bce1477a6a68d4373"}, - {file = "coverage-6.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bc1ee1318f703bc6c971da700d74466e9b86e0c443eb85983fb2a1bd20447263"}, - {file = "coverage-6.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3f546f48d5d80a90a266769aa613bc0719cb3e9c2ef3529d53f463996dd15a9d"}, - {file = "coverage-6.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd92ece726055e80d4e3f01fff3b91f54b18c9c357c48fcf6119e87e2461a091"}, - {file = "coverage-6.1.2-cp38-cp38-win32.whl", hash = "sha256:24ed38ec86754c4d5a706fbd5b52b057c3df87901a8610d7e5642a08ec07087e"}, - {file = "coverage-6.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:97ef6e9119bd39d60ef7b9cd5deea2b34869c9f0b9777450a7e3759c1ab09b9b"}, - {file = "coverage-6.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e5a8c947a2a89c56655ecbb789458a3a8e3b0cbf4c04250331df8f647b3de59"}, - {file = "coverage-6.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a39590d1e6acf6a3c435c5d233f72f5d43b585f5be834cff1f21fec4afda225"}, - {file = "coverage-6.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9d2c2e3ce7b8cc932a2f918186964bd44de8c84e2f9ef72dc616f5bb8be22e71"}, - {file = "coverage-6.1.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3348865798c077c695cae00da0924136bb5cc501f236cfd6b6d9f7a3c94e0ec4"}, - {file = "coverage-6.1.2-cp39-cp39-win32.whl", hash = "sha256:fae3fe111670e51f1ebbc475823899524e3459ea2db2cb88279bbfb2a0b8a3de"}, - {file = "coverage-6.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:af45eea024c0e3a25462fade161afab4f0d9d9e0d5a5d53e86149f74f0a35ecc"}, - {file = "coverage-6.1.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:eab14fdd410500dae50fd14ccc332e65543e7b39f6fc076fe90603a0e5d2f929"}, - {file = "coverage-6.1.2.tar.gz", hash = "sha256:d9a635114b88c0ab462e0355472d00a180a5fbfd8511e7f18e4ac32652e7d972"}, + {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, + {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, + {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, + {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, + {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, + {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, + {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, + {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, + {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, + {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, + {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, + {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, + {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, + {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, + {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, + {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, + {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, + {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, + {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, + {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, ] coveralls = [ {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, @@ -898,12 +899,12 @@ filelock = [ {file = "filelock-3.4.0.tar.gz", hash = "sha256:93d512b32a23baf4cac44ffd72ccf70732aeff7b8050fcaf6d3ec406d954baf4"}, ] furo = [ - {file = "furo-2021.11.16-py3-none-any.whl", hash = "sha256:9bbdf203e84e048391b58a3bc2d4baf13f462604661553cf72e22eae61db116a"}, - {file = "furo-2021.11.16.tar.gz", hash = "sha256:bb1d1a088cd09d4e9aaac908230f819ebdc94baa007d0561a2034b37dcb8ea6c"}, + {file = "furo-2021.11.23-py3-none-any.whl", hash = "sha256:6d396451ad1aadce380c662fca9362cb10f4fd85f296d74fe3ca32006eb641d7"}, + {file = "furo-2021.11.23.tar.gz", hash = "sha256:54cecac5f3b688b5c7370d72ecdf1cd91a6c53f0f42751f4a719184b562cde70"}, ] identify = [ - {file = "identify-2.3.6-py2.py3-none-any.whl", hash = "sha256:8cb609a671d2f861ae7fe583711a43fd2faab0c892f39cbc4568b0c51b354238"}, - {file = "identify-2.3.6.tar.gz", hash = "sha256:4f85f9bd8e6e5e2d61b2f8de5ff5313d8a1cfac4c88822d74406de45ad10bd82"}, + {file = "identify-2.4.0-py2.py3-none-any.whl", hash = "sha256:eba31ca80258de6bb51453084bff4a923187cd2193b9c13710f2516ab30732cc"}, + {file = "identify-2.4.0.tar.gz", hash = "sha256:a33ae873287e81651c7800ca309dc1f84679b763c9c8b30680e16fbfa82f0107"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, @@ -1025,8 +1026,8 @@ pre-commit = [ {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.22-py3-none-any.whl", hash = "sha256:48d85cdca8b6c4f16480c7ce03fd193666b62b0a21667ca56b4bb5ad679d1170"}, - {file = "prompt_toolkit-3.0.22.tar.gz", hash = "sha256:449f333dd120bd01f5d296a8ce1452114ba3a71fae7288d2f0ae2c918764fa72"}, + {file = "prompt_toolkit-3.0.23-py3-none-any.whl", hash = "sha256:5f29d62cb7a0ecacfa3d8ceea05a63cd22500543472d64298fc06ddda906b25d"}, + {file = "prompt_toolkit-3.0.23.tar.gz", hash = "sha256:7053aba00895473cb357819358ef33f11aa97e4ac83d38efb123e5649ceeecaf"}, ] pydocstyle = [ {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, @@ -1147,8 +1148,8 @@ soupsieve = [ {file = "soupsieve-2.3.1.tar.gz", hash = "sha256:b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9"}, ] sphinx = [ - {file = "Sphinx-4.3.0-py3-none-any.whl", hash = "sha256:7e2b30da5f39170efcd95c6270f07669d623c276521fee27ad6c380f49d2bf5b"}, - {file = "Sphinx-4.3.0.tar.gz", hash = "sha256:6d051ab6e0d06cba786c4656b0fe67ba259fe058410f49e95bee6e49c4052cbf"}, + {file = "Sphinx-4.3.1-py3-none-any.whl", hash = "sha256:048dac56039a5713f47a554589dc98a442b39226a2b9ed7f82797fcb2fe9253f"}, + {file = "Sphinx-4.3.1.tar.gz", hash = "sha256:32a5b3e9a1b176cc25ed048557d4d3d01af635e6b76c5bc7a43b0a34447fbd45"}, ] sphinx-autobuild = [ {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, From 41b9b5ef05bb475d5cf95d411347dd2d8db0f8f7 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Wed, 1 Dec 2021 10:00:15 +1100 Subject: [PATCH 14/41] fix(number): no zero input --- InquirerPy/prompts/number.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 217623a..97fa433 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -1,4 +1,5 @@ """Module contains the class to create a number prompt.""" +import re from typing import TYPE_CHECKING, Any, Callable, Union, cast from prompt_toolkit.application.application import Application @@ -95,7 +96,8 @@ def __init__( self._min = min_allowed self._value_error_message = "Remove any non-integer value" self._decimal_symbol = decimal_symbol - self._ending_zero = False + self._ending_zero = "" + self._zero_pattern = re.compile(r"^[0-9]+?(0+)$") if isinstance(default, Callable): default = cast(Callable, default)(session_result) @@ -366,11 +368,11 @@ def value(self) -> Union[int, float]: if not self._float: return int(self._whole_buffer.text) else: - self._ending_zero = ( - self._integral_buffer.text.endswith("0") - if len(self._integral_buffer.text) > 1 - else False - ) + ending_zero = self._zero_pattern.match(self._integral_buffer.text) + if ending_zero is not None: + self._ending_zero = ending_zero.group(1) + else: + self._ending_zero = "" return float(f"{self._whole_buffer.text}.{self._integral_buffer.text}") except ValueError: self._set_error(self._value_error_message) @@ -386,7 +388,4 @@ def value(self, value: Union[int, float]) -> None: self._whole_buffer.text = str(value) else: self._whole_buffer.text, integral_buffer_text = str(value).split(".") - if self._ending_zero: - self._integral_buffer.text = integral_buffer_text + "0" - else: - self._integral_buffer.text = integral_buffer_text + self._integral_buffer.text = integral_buffer_text + self._ending_zero From a628087a8875f4dbb0168e582cdbc806779b9ecb Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Wed, 1 Dec 2021 11:40:38 +1100 Subject: [PATCH 15/41] fix(number): empty toggle error --- InquirerPy/prompts/number.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 97fa433..0e7990c 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -320,6 +320,9 @@ def _handle_input(self, event: "KeyPressEvent") -> None: self.focus_buffer.insert_text(event.key_sequence[0].data) def _handle_negative_toggle(self, _) -> None: + if self._whole_buffer.text == "-": + self._whole_buffer.text = "0" + return if self._whole_buffer.text.startswith("-"): self._whole_buffer.text = self._whole_buffer.text[1:] else: From ab5a2ae6d80770820b69d9e67941b7d39c000c09 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Wed, 1 Dec 2021 11:40:59 +1100 Subject: [PATCH 16/41] feat(number): set default value --- InquirerPy/prompts/number.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 0e7990c..3cc4de1 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -258,10 +258,15 @@ def _(_): ) def _on_rendered(self, _) -> None: - self._whole_buffer.text = "0" - self._whole_buffer.cursor_position = 0 - self._integral_buffer.text = "0" - self._integral_buffer.cursor_position = 0 + if not self._float: + self._whole_buffer.text = str(self._default) + self._integral_buffer.text = "0" + else: + self._whole_buffer.text, self._integral_buffer.text = str( + self._default + ).split(".") + self._whole_buffer.cursor_position = len(self._whole_buffer.text) + self._integral_buffer.cursor_position = len(self._integral_buffer.text) def _handle_down(self, _) -> None: try: From 108b134128d7ae08066b4e3427aabea16407a90a Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Wed, 1 Dec 2021 12:39:15 +1100 Subject: [PATCH 17/41] feat(number): handle answer --- InquirerPy/prompts/number.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 3cc4de1..34c5f1b 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -22,8 +22,9 @@ from prompt_toolkit.layout.dimension import Dimension, LayoutDimension from prompt_toolkit.layout.layout import Layout from prompt_toolkit.lexers.base import SimpleLexer +from prompt_toolkit.validation import ValidationError -from InquirerPy.base.complex import BaseComplexPrompt +from InquirerPy.base.complex import BaseComplexPrompt, FakeDocument from InquirerPy.containers.instruction import InstructionWindow from InquirerPy.containers.validation import ValidationWindow from InquirerPy.enum import INQUIRERPY_QMARK_SEQUENCE @@ -209,7 +210,7 @@ def _(_): dont_extend_height=True, dont_extend_width=True, ), - self._whole_window, + ConditionalContainer(self._whole_window, filter=~IsDone()), ConditionalContainer( Window( height=LayoutDimension.exact(1) @@ -222,10 +223,10 @@ def _(_): dont_extend_height=True, dont_extend_width=True, ), - filter=self._is_float, + filter=self._is_float & ~IsDone(), ), ConditionalContainer( - self._integral_window, filter=self._is_float + self._integral_window, filter=self._is_float & ~IsDone() ), ], align=HorizontalAlign.LEFT, @@ -310,8 +311,16 @@ def _handle_right(self, _) -> None: else: self.focus_buffer.cursor_position += 1 - def _handle_enter(self, _) -> None: - pass + def _handle_enter(self, event) -> None: + try: + fake_document = FakeDocument(str(self.value)) + self._validator.validate(fake_document) # type: ignore + except ValidationError as e: + self._set_error(str(e)) + else: + self.status["answered"] = True + self.status["result"] = self.value + event.app.exit(result=self.value) def _handle_focus(self, _) -> None: if not self._float: From 2bd573ac9c14d4c8d827e138a8a9f1323f18ad1e Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Wed, 1 Dec 2021 12:50:42 +1100 Subject: [PATCH 18/41] test(number): init --- examples/alternate/number.py | 1 + tests/prompts/test_number.py | 66 ++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/prompts/test_number.py diff --git a/examples/alternate/number.py b/examples/alternate/number.py index 503e6f5..d118607 100644 --- a/examples/alternate/number.py +++ b/examples/alternate/number.py @@ -6,5 +6,6 @@ raise_keyboard_interrupt=False, min_allowed=-10, max_allowed=10, + default="2.7", float_allowed=True, ).execute() diff --git a/tests/prompts/test_number.py b/tests/prompts/test_number.py new file mode 100644 index 0000000..2c05e30 --- /dev/null +++ b/tests/prompts/test_number.py @@ -0,0 +1,66 @@ +import unittest +from unittest.mock import ANY, call, patch + +from prompt_toolkit.keys import Keys + +from InquirerPy.prompts.number import NumberPrompt + + +class TestNumberPrompt(unittest.TestCase): + def setUp(self) -> None: + self.prompt = NumberPrompt(message="Hello", default=1) + self.float_prompt = NumberPrompt(message="Hello", float_allowed=True, default=1) + + def test_contructor(self) -> None: + self.assertFalse(self.prompt._float) + self.assertEqual(self.prompt._default, 1) + self.assertFalse(self.prompt._is_float()) + self.assertEqual(self.prompt.focus, self.prompt._whole_window) + + def test_float_constructor(self) -> None: + self.assertTrue(self.float_prompt._float) + self.assertEqual(self.float_prompt._default, 1.0) + self.assertTrue(self.float_prompt._is_float()) + + @patch("InquirerPy.prompts.number.NumberPrompt.register_kb") + def test_kb_registered(self, mocked_kb) -> None: + prompt = NumberPrompt(message="") + mocked_kb.assert_has_calls([call(Keys.Any)]) + + prompt._after_render(None) + mocked_kb.assert_has_calls([call("down", filter=ANY)]) + mocked_kb.assert_has_calls([call("up", filter=ANY)]) + mocked_kb.assert_has_calls([call("left", filter=ANY)]) + mocked_kb.assert_has_calls([call(Keys.Tab, filter=ANY)]) + for i in range(10): + mocked_kb.assert_has_calls([call(str(i), filter=ANY)]) + + def test_on_rendered(self) -> None: + self.prompt._on_rendered(None) + self.assertEqual(self.prompt._whole_buffer.text, "1") + self.assertEqual(self.prompt._integral_buffer.text, "0") + + self.float_prompt._default = 1.1 + self.float_prompt._on_rendered(None) + self.assertEqual(self.float_prompt._whole_buffer.text, "1") + self.assertEqual(self.float_prompt._integral_buffer.text, "1") + + def test_handle_down(self) -> None: + self.prompt._on_rendered(None) + self.prompt._handle_down(None) + self.assertEqual(self.prompt._whole_buffer.text, "0") + self.prompt._handle_down(None) + self.assertEqual(self.prompt._whole_buffer.text, "-1") + self.assertEqual(self.prompt._integral_buffer.text, "0") + + def test_handle_down_float(self) -> None: + self.float_prompt._default = 0.3 + self.float_prompt._on_rendered(None) + self.float_prompt._handle_focus(None) + self.float_prompt._handle_down(None) + self.assertEqual(self.float_prompt._integral_buffer.text, "2") + self.float_prompt._handle_down(None) + self.float_prompt._handle_down(None) + self.float_prompt._handle_down(None) + self.float_prompt._handle_down(None) + self.assertEqual(self.float_prompt._integral_buffer.text, "0") From 85366918f93292d09e4f2a9339ba469be6919f07 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Thu, 2 Dec 2021 09:07:16 +1100 Subject: [PATCH 19/41] feat(number): force empty result when empty input --- InquirerPy/prompts/number.py | 9 ++++++--- examples/alternate/number.py | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 34c5f1b..f828ba8 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -312,15 +312,18 @@ def _handle_right(self, _) -> None: self.focus_buffer.cursor_position += 1 def _handle_enter(self, event) -> None: + result = str(self.value) + if not self._whole_buffer.text and not self._integral_buffer.text: + result = "" try: - fake_document = FakeDocument(str(self.value)) + fake_document = FakeDocument(result) self._validator.validate(fake_document) # type: ignore except ValidationError as e: self._set_error(str(e)) else: self.status["answered"] = True - self.status["result"] = self.value - event.app.exit(result=self.value) + self.status["result"] = result + event.app.exit(result=result) def _handle_focus(self, _) -> None: if not self._float: diff --git a/examples/alternate/number.py b/examples/alternate/number.py index d118607..9b31c2a 100644 --- a/examples/alternate/number.py +++ b/examples/alternate/number.py @@ -1,4 +1,5 @@ from InquirerPy import inquirer +from InquirerPy.validator import EmptyInputValidator result = inquirer.number( message="hello", @@ -8,4 +9,5 @@ max_allowed=10, default="2.7", float_allowed=True, + validate=EmptyInputValidator(), ).execute() From 7e40e8b6168292598ffa4bee13923a187f73e1bd Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Thu, 2 Dec 2021 09:30:52 +1100 Subject: [PATCH 20/41] test(number): handle_left handle_down --- tests/prompts/test_number.py | 69 ++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/tests/prompts/test_number.py b/tests/prompts/test_number.py index 2c05e30..c44500d 100644 --- a/tests/prompts/test_number.py +++ b/tests/prompts/test_number.py @@ -8,8 +8,16 @@ class TestNumberPrompt(unittest.TestCase): def setUp(self) -> None: - self.prompt = NumberPrompt(message="Hello", default=1) - self.float_prompt = NumberPrompt(message="Hello", float_allowed=True, default=1) + self.prompt = NumberPrompt( + message="Hello", default=1, max_allowed=10, min_allowed=-2 + ) + self.float_prompt = NumberPrompt( + message="Hello", + float_allowed=True, + default=1, + max_allowed=10, + min_allowed=-2, + ) def test_contructor(self) -> None: self.assertFalse(self.prompt._float) @@ -51,7 +59,10 @@ def test_handle_down(self) -> None: self.assertEqual(self.prompt._whole_buffer.text, "0") self.prompt._handle_down(None) self.assertEqual(self.prompt._whole_buffer.text, "-1") - self.assertEqual(self.prompt._integral_buffer.text, "0") + self.prompt._handle_down(None) + self.prompt._handle_down(None) + self.prompt._handle_down(None) + self.assertEqual(self.prompt._whole_buffer.text, "-2") def test_handle_down_float(self) -> None: self.float_prompt._default = 0.3 @@ -64,3 +75,55 @@ def test_handle_down_float(self) -> None: self.float_prompt._handle_down(None) self.float_prompt._handle_down(None) self.assertEqual(self.float_prompt._integral_buffer.text, "0") + + def test_handle_up(self) -> None: + self.prompt._on_rendered(None) + self.prompt._handle_up(None) + self.assertEqual(self.prompt._whole_buffer.text, "2") + self.prompt._handle_up(None) + self.prompt._handle_up(None) + self.prompt._handle_up(None) + self.assertEqual(self.prompt._whole_buffer.text, "5") + self.prompt._handle_up(None) + self.prompt._handle_up(None) + self.prompt._handle_up(None) + self.prompt._handle_up(None) + self.prompt._handle_up(None) + self.prompt._handle_up(None) + self.prompt._handle_up(None) + self.assertEqual(self.prompt._whole_buffer.text, "10") + + def test_handle_up_float(self) -> None: + self.float_prompt._default = 9.0 + self.float_prompt._on_rendered(None) + self.float_prompt._handle_focus(None) + self.float_prompt._handle_up(None) + self.assertEqual(self.float_prompt._integral_buffer.text, "1") + self.float_prompt._handle_up(None) + self.assertEqual(self.float_prompt._integral_buffer.text, "2") + self.float_prompt._handle_focus(None) + self.float_prompt._handle_up(None) + self.assertEqual(self.float_prompt._integral_buffer.text, "0") + + def test_handle_left(self) -> None: + self.prompt._on_rendered(None) + self.assertEqual(self.prompt._whole_buffer.cursor_position, 1) + self.prompt._handle_left(None) + self.assertEqual(self.prompt._whole_buffer.cursor_position, 0) + self.prompt._handle_left(None) + self.assertEqual(self.prompt._whole_buffer.cursor_position, 0) + + def test_handle_left_float(self) -> None: + self.float_prompt._on_rendered(None) + self.assertEqual(self.float_prompt._whole_buffer.cursor_position, 1) + self.assertEqual(self.float_prompt._integral_buffer.cursor_position, 1) + self.float_prompt._handle_focus(None) + self.float_prompt._handle_left(None) + self.assertEqual(self.float_prompt._integral_buffer.cursor_position, 0) + self.assertEqual(self.float_prompt.focus, self.float_prompt._integral_window) + self.float_prompt._handle_left(None) + self.assertEqual(self.float_prompt._integral_buffer.cursor_position, 0) + self.assertEqual(self.float_prompt.focus, self.float_prompt._whole_window) + self.assertEqual(self.float_prompt._whole_buffer.cursor_position, 1) + self.float_prompt._handle_left(None) + self.assertEqual(self.float_prompt._whole_buffer.cursor_position, 0) From abf78ce37adf1c33acb5edb7ea08c0fd91578127 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Mon, 6 Dec 2021 08:40:38 +1100 Subject: [PATCH 21/41] chore: update dev dependencies --- poetry.lock | 103 +++++++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/poetry.lock b/poetry.lock index 20bb5e2..3d20f55 100644 --- a/poetry.lock +++ b/poetry.lock @@ -107,7 +107,7 @@ python-versions = ">=3.6.1" [[package]] name = "charset-normalizer" -version = "2.0.8" +version = "2.0.9" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -426,7 +426,7 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock [[package]] name = "pre-commit" -version = "2.15.0" +version = "2.16.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -710,7 +710,7 @@ python-versions = ">= 3.5" [[package]] name = "typed-ast" -version = "1.5.0" +version = "1.5.1" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -718,7 +718,7 @@ python-versions = ">=3.6" [[package]] name = "typing-extensions" -version = "4.0.0" +version = "4.0.1" description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false @@ -819,8 +819,8 @@ cfgv = [ {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.8.tar.gz", hash = "sha256:735e240d9a8506778cd7a453d97e817e536bb1fc29f4f6961ce297b9c7a917b0"}, - {file = "charset_normalizer-2.0.8-py3-none-any.whl", hash = "sha256:83fcdeb225499d6344c8f7f34684c2981270beacc32ede2e669e94f7fa544405"}, + {file = "charset-normalizer-2.0.9.tar.gz", hash = "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"}, + {file = "charset_normalizer-2.0.9-py3-none-any.whl", hash = "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721"}, ] click = [ {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, @@ -934,22 +934,12 @@ markdown-it-py = [ {file = "markdown_it_py-1.1.0-py3-none-any.whl", hash = "sha256:98080fc0bc34c4f2bcf0846a096a9429acbd9d5d8e67ed34026c03c61c464389"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -958,21 +948,14 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -982,9 +965,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -1022,8 +1002,8 @@ platformdirs = [ {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, ] pre-commit = [ - {file = "pre_commit-2.15.0-py2.py3-none-any.whl", hash = "sha256:a4ed01000afcb484d9eb8d504272e642c4c4099bbad3a6b27e519bd6a3e928a6"}, - {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"}, + {file = "pre_commit-2.16.0-py2.py3-none-any.whl", hash = "sha256:758d1dc9b62c2ed8881585c254976d66eae0889919ab9b859064fc2fe3c7743e"}, + {file = "pre_commit-2.16.0.tar.gz", hash = "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65"}, ] prompt-toolkit = [ {file = "prompt_toolkit-3.0.23-py3-none-any.whl", hash = "sha256:5f29d62cb7a0ecacfa3d8ceea05a63cd22500543472d64298fc06ddda906b25d"}, @@ -1089,6 +1069,11 @@ regex = [ {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f"}, {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0"}, {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9ed0b1e5e0759d6b7f8e2f143894b2a7f3edd313f38cf44e1e15d360e11749b"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:473e67837f786404570eae33c3b64a4b9635ae9f00145250851a1292f484c063"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2fee3ed82a011184807d2127f1733b4f6b2ff6ec7151d83ef3477f3b96a13d03"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d5fd67df77bab0d3f4ea1d7afca9ef15c2ee35dfb348c7b57ffb9782a6e4db6e"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5d408a642a5484b9b4d11dea15a489ea0928c7e410c7525cd892f4d04f2f617b"}, {file = "regex-2021.11.10-cp310-cp310-win32.whl", hash = "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a"}, {file = "regex-2021.11.10-cp310-cp310-win_amd64.whl", hash = "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12"}, {file = "regex-2021.11.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc"}, @@ -1098,6 +1083,11 @@ regex = [ {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733"}, {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23"}, {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:74cbeac0451f27d4f50e6e8a8f3a52ca074b5e2da9f7b505c4201a57a8ed6286"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:3598893bde43091ee5ca0a6ad20f08a0435e93a69255eeb5f81b85e81e329264"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:50a7ddf3d131dc5633dccdb51417e2d1910d25cbcf842115a3a5893509140a3a"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:61600a7ca4bcf78a96a68a27c2ae9389763b5b94b63943d5158f2a377e09d29a"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:563d5f9354e15e048465061509403f68424fef37d5add3064038c2511c8f5e00"}, {file = "regex-2021.11.10-cp36-cp36m-win32.whl", hash = "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4"}, {file = "regex-2021.11.10-cp36-cp36m-win_amd64.whl", hash = "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e"}, {file = "regex-2021.11.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e"}, @@ -1107,6 +1097,11 @@ regex = [ {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a"}, {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e"}, {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:42b50fa6666b0d50c30a990527127334d6b96dd969011e843e726a64011485da"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6e1d2cc79e8dae442b3fa4a26c5794428b98f81389af90623ffcc650ce9f6732"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:0416f7399e918c4b0e074a0f66e5191077ee2ca32a0f99d4c187a62beb47aa05"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ce298e3d0c65bd03fa65ffcc6db0e2b578e8f626d468db64fdf8457731052942"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dc07f021ee80510f3cd3af2cad5b6a3b3a10b057521d9e6aaeb621730d320c5a"}, {file = "regex-2021.11.10-cp37-cp37m-win32.whl", hash = "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec"}, {file = "regex-2021.11.10-cp37-cp37m-win_amd64.whl", hash = "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4"}, {file = "regex-2021.11.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83"}, @@ -1117,6 +1112,11 @@ regex = [ {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec"}, {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe"}, {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f5be7805e53dafe94d295399cfbe5227f39995a997f4fd8539bf3cbdc8f47ca8"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a955b747d620a50408b7fdf948e04359d6e762ff8a85f5775d907ceced715129"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:139a23d1f5d30db2cc6c7fd9c6d6497872a672db22c4ae1910be22d4f4b2068a"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ca49e1ab99593438b204e00f3970e7a5f70d045267051dfa6b5f4304fcfa1dbf"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:96fc32c16ea6d60d3ca7f63397bff5c75c5a562f7db6dec7d412f7c4d2e78ec0"}, {file = "regex-2021.11.10-cp38-cp38-win32.whl", hash = "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc"}, {file = "regex-2021.11.10-cp38-cp38-win_amd64.whl", hash = "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d"}, {file = "regex-2021.11.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b"}, @@ -1127,6 +1127,11 @@ regex = [ {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449"}, {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b"}, {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cd410a1cbb2d297c67d8521759ab2ee3f1d66206d2e4328502a487589a2cb21b"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e6096b0688e6e14af6a1b10eaad86b4ff17935c49aa774eac7c95a57a4e8c296"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:529801a0d58809b60b3531ee804d3e3be4b412c94b5d267daa3de7fadef00f49"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f594b96fe2e0821d026365f72ac7b4f0b487487fb3d4aaf10dd9d97d88a9737"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2409b5c9cef7054dde93a9803156b411b677affc84fca69e908b1cb2c540025d"}, {file = "regex-2021.11.10-cp39-cp39-win32.whl", hash = "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a"}, {file = "regex-2021.11.10-cp39-cp39-win_amd64.whl", hash = "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29"}, {file = "regex-2021.11.10.tar.gz", hash = "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6"}, @@ -1235,29 +1240,29 @@ tornado = [ {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"}, ] typed-ast = [ - {file = "typed_ast-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b310a207ee9fde3f46ba327989e6cba4195bc0c8c70a158456e7b10233e6bed"}, - {file = "typed_ast-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52ca2b2b524d770bed7a393371a38e91943f9160a190141e0df911586066ecda"}, - {file = "typed_ast-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:14fed8820114a389a2b7e91624db5f85f3f6682fda09fe0268a59aabd28fe5f5"}, - {file = "typed_ast-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:65c81abbabda7d760df7304d843cc9dbe7ef5d485504ca59a46ae2d1731d2428"}, - {file = "typed_ast-1.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:37ba2ab65a0028b1a4f2b61a8fe77f12d242731977d274a03d68ebb751271508"}, - {file = "typed_ast-1.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:49af5b8f6f03ed1eb89ee06c1d7c2e7c8e743d720c3746a5857609a1abc94c94"}, - {file = "typed_ast-1.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e4374a76e61399a173137e7984a1d7e356038cf844f24fd8aea46c8029a2f712"}, - {file = "typed_ast-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ea517c2bb11c5e4ba7a83a91482a2837041181d57d3ed0749a6c382a2b6b7086"}, - {file = "typed_ast-1.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:51040bf45aacefa44fa67fb9ebcd1f2bec73182b99a532c2394eea7dabd18e24"}, - {file = "typed_ast-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:806e0c7346b9b4af8c62d9a29053f484599921a4448c37fbbcbbf15c25138570"}, - {file = "typed_ast-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a67fd5914603e2165e075f1b12f5a8356bfb9557e8bfb74511108cfbab0f51ed"}, - {file = "typed_ast-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:224afecb8b39739f5c9562794a7c98325cb9d972712e1a98b6989a4720219541"}, - {file = "typed_ast-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:155b74b078be842d2eb630dd30a280025eca0a5383c7d45853c27afee65f278f"}, - {file = "typed_ast-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:361b9e5d27bd8e3ccb6ea6ad6c4f3c0be322a1a0f8177db6d56264fa0ae40410"}, - {file = "typed_ast-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:618912cbc7e17b4aeba86ffe071698c6e2d292acbd6d1d5ec1ee724b8c4ae450"}, - {file = "typed_ast-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7e6731044f748340ef68dcadb5172a4b1f40847a2983fe3983b2a66445fbc8e6"}, - {file = "typed_ast-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e8a9b9c87801cecaad3b4c2b8876387115d1a14caa602c1618cedbb0cb2a14e6"}, - {file = "typed_ast-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:ec184dfb5d3d11e82841dbb973e7092b75f306b625fad7b2e665b64c5d60ab3f"}, - {file = "typed_ast-1.5.0.tar.gz", hash = "sha256:ff4ad88271aa7a55f19b6a161ed44e088c393846d954729549e3cde8257747bb"}, + {file = "typed_ast-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d8314c92414ce7481eee7ad42b353943679cf6f30237b5ecbf7d835519e1212"}, + {file = "typed_ast-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b53ae5de5500529c76225d18eeb060efbcec90ad5e030713fe8dab0fb4531631"}, + {file = "typed_ast-1.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:24058827d8f5d633f97223f5148a7d22628099a3d2efe06654ce872f46f07cdb"}, + {file = "typed_ast-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a6d495c1ef572519a7bac9534dbf6d94c40e5b6a608ef41136133377bba4aa08"}, + {file = "typed_ast-1.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:de4ecae89c7d8b56169473e08f6bfd2df7f95015591f43126e4ea7865928677e"}, + {file = "typed_ast-1.5.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:256115a5bc7ea9e665c6314ed6671ee2c08ca380f9d5f130bd4d2c1f5848d695"}, + {file = "typed_ast-1.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:7c42707ab981b6cf4b73490c16e9d17fcd5227039720ca14abe415d39a173a30"}, + {file = "typed_ast-1.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:71dcda943a471d826ea930dd449ac7e76db7be778fcd722deb63642bab32ea3f"}, + {file = "typed_ast-1.5.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4f30a2bcd8e68adbb791ce1567fdb897357506f7ea6716f6bbdd3053ac4d9471"}, + {file = "typed_ast-1.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ca9e8300d8ba0b66d140820cf463438c8e7b4cdc6fd710c059bfcfb1531d03fb"}, + {file = "typed_ast-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9caaf2b440efb39ecbc45e2fabde809cbe56272719131a6318fd9bf08b58e2cb"}, + {file = "typed_ast-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c9bcad65d66d594bffab8575f39420fe0ee96f66e23c4d927ebb4e24354ec1af"}, + {file = "typed_ast-1.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:591bc04e507595887160ed7aa8d6785867fb86c5793911be79ccede61ae96f4d"}, + {file = "typed_ast-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:a80d84f535642420dd17e16ae25bb46c7f4c16ee231105e7f3eb43976a89670a"}, + {file = "typed_ast-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:38cf5c642fa808300bae1281460d4f9b7617cf864d4e383054a5ef336e344d32"}, + {file = "typed_ast-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b6ab14c56bc9c7e3c30228a0a0b54b915b1579613f6e463ba6f4eb1382e7fd4"}, + {file = "typed_ast-1.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2b8d7007f6280e36fa42652df47087ac7b0a7d7f09f9468f07792ba646aac2d"}, + {file = "typed_ast-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:b6d17f37f6edd879141e64a5db17b67488cfeffeedad8c5cec0392305e9bc775"}, + {file = "typed_ast-1.5.1.tar.gz", hash = "sha256:484137cab8ecf47e137260daa20bafbba5f4e3ec7fda1c1e69ab299b75fa81c5"}, ] typing-extensions = [ - {file = "typing_extensions-4.0.0-py3-none-any.whl", hash = "sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9"}, - {file = "typing_extensions-4.0.0.tar.gz", hash = "sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed"}, + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, ] urllib3 = [ {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, From 79da5f87bd407db8c96a0c5ae92e3fab4600de9b Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Mon, 6 Dec 2021 08:47:15 +1100 Subject: [PATCH 22/41] fix(number): reset floating number starting at 0 --- InquirerPy/prompts/number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index f828ba8..8f554c5 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -267,7 +267,7 @@ def _on_rendered(self, _) -> None: self._default ).split(".") self._whole_buffer.cursor_position = len(self._whole_buffer.text) - self._integral_buffer.cursor_position = len(self._integral_buffer.text) + self._integral_buffer.cursor_position = 0 def _handle_down(self, _) -> None: try: From 19c7cb8ba9148847578c7accdc3e2a58960a50d6 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Mon, 6 Dec 2021 08:47:24 +1100 Subject: [PATCH 23/41] test(number): handle_right --- tests/prompts/test_number.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/prompts/test_number.py b/tests/prompts/test_number.py index c44500d..0c557c2 100644 --- a/tests/prompts/test_number.py +++ b/tests/prompts/test_number.py @@ -116,7 +116,8 @@ def test_handle_left(self) -> None: def test_handle_left_float(self) -> None: self.float_prompt._on_rendered(None) self.assertEqual(self.float_prompt._whole_buffer.cursor_position, 1) - self.assertEqual(self.float_prompt._integral_buffer.cursor_position, 1) + self.assertEqual(self.float_prompt._integral_buffer.cursor_position, 0) + self.float_prompt._integral_buffer.cursor_position = 1 self.float_prompt._handle_focus(None) self.float_prompt._handle_left(None) self.assertEqual(self.float_prompt._integral_buffer.cursor_position, 0) @@ -127,3 +128,28 @@ def test_handle_left_float(self) -> None: self.assertEqual(self.float_prompt._whole_buffer.cursor_position, 1) self.float_prompt._handle_left(None) self.assertEqual(self.float_prompt._whole_buffer.cursor_position, 0) + + def test_handle_right(self) -> None: + self.prompt._on_rendered(None) + self.assertEqual(self.prompt._whole_buffer.cursor_position, 1) + self.assertEqual(self.prompt.focus, self.prompt._whole_window) + self.prompt._handle_right(None) + self.assertEqual(self.prompt._whole_buffer.cursor_position, 1) + self.assertNotEqual(self.prompt.focus, self.prompt._integral_window) + + self.prompt._whole_buffer.cursor_position = 0 + self.assertEqual(self.prompt._whole_buffer.cursor_position, 0) + self.prompt._handle_right(None) + self.assertEqual(self.prompt._whole_buffer.cursor_position, 1) + + def test_handle_right_float(self) -> None: + self.float_prompt._on_rendered(None) + self.assertEqual(self.float_prompt._whole_buffer.cursor_position, 1) + self.assertEqual(self.float_prompt.focus, self.float_prompt._whole_window) + self.float_prompt._handle_right(None) + self.assertEqual(self.float_prompt.focus, self.float_prompt._integral_window) + self.assertEqual(self.float_prompt._integral_buffer.cursor_position, 0) + self.float_prompt._handle_right(None) + self.assertEqual(self.float_prompt._integral_buffer.cursor_position, 1) + self.float_prompt._handle_right(None) + self.assertEqual(self.float_prompt._integral_buffer.cursor_position, 1) From 79ef0dd0565f6523348908058900bde05a90ba06 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Mon, 6 Dec 2021 09:02:34 +1100 Subject: [PATCH 24/41] test(number): handle input and answer --- tests/prompts/test_number.py | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/prompts/test_number.py b/tests/prompts/test_number.py index 0c557c2..3d955b6 100644 --- a/tests/prompts/test_number.py +++ b/tests/prompts/test_number.py @@ -18,6 +18,8 @@ def setUp(self) -> None: max_allowed=10, min_allowed=-2, ) + self.prompt._on_rendered(None) + self.float_prompt._on_rendered(None) def test_contructor(self) -> None: self.assertFalse(self.prompt._float) @@ -153,3 +155,49 @@ def test_handle_right_float(self) -> None: self.assertEqual(self.float_prompt._integral_buffer.cursor_position, 1) self.float_prompt._handle_right(None) self.assertEqual(self.float_prompt._integral_buffer.cursor_position, 1) + + def test_handle_enter(self) -> None: + self.prompt._on_rendered(None) + with patch("prompt_toolkit.utils.Event") as mock: + event = mock.return_value + self.prompt._handle_enter(event) + self.assertTrue(self.prompt.status["answered"]) + self.assertEqual(self.prompt.status["result"], "1") + + def test_handle_enter_float(self) -> None: + self.float_prompt._on_rendered(None) + with patch("prompt_toolkit.utils.Event") as mock: + event = mock.return_value + self.float_prompt._handle_enter(event) + self.assertTrue(self.float_prompt.status["answered"]) + self.assertEqual(self.float_prompt.status["result"], "1.0") + + def test_handle_enter_validation(self) -> None: + prompt = NumberPrompt(message="", validate=lambda x: x == 1) + prompt._on_rendered(None) + with patch("prompt_toolkit.utils.Event") as mock: + event = mock.return_value + prompt._handle_enter(event) + self.assertFalse(prompt.status["answered"]) + self.assertEqual(prompt.status["result"], None) + self.assertEqual( + prompt._get_error_message(), [("class:validation-toolbar", "Invalid input")] + ) + + def test_handle_focus(self) -> None: + self.assertEqual(self.prompt.focus, self.prompt._whole_window) + self.prompt._handle_focus(None) + self.assertEqual(self.prompt.focus, self.prompt._whole_window) + + def test_handle_focus_float(self) -> None: + self.assertEqual(self.float_prompt.focus, self.float_prompt._whole_window) + self.float_prompt._handle_focus(None) + self.assertEqual(self.float_prompt.focus, self.float_prompt._integral_window) + self.float_prompt._handle_focus(None) + self.assertEqual(self.float_prompt.focus, self.float_prompt._whole_window) + + def test_handle_input(self) -> None: + with patch("prompt_toolkit.utils.Event") as mock: + event = mock.return_value + self.prompt._whole_buffer.cursor_position = 0 + self.prompt._handle_input(event) From 8146c678b275293569fadf6c38fcbfe7fb41526e Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Mon, 6 Dec 2021 17:06:08 +1100 Subject: [PATCH 25/41] test(number): handle text change --- tests/prompts/test_number.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/prompts/test_number.py b/tests/prompts/test_number.py index 3d955b6..417777b 100644 --- a/tests/prompts/test_number.py +++ b/tests/prompts/test_number.py @@ -201,3 +201,16 @@ def test_handle_input(self) -> None: event = mock.return_value self.prompt._whole_buffer.cursor_position = 0 self.prompt._handle_input(event) + + @patch("InquirerPy.prompts.number.NumberPrompt._on_text_change") + def test_on_text_change(self, mocked_text) -> None: + self.prompt._whole_buffer.text = "10" + mocked_text.assert_called() + self.assertEqual(self.prompt._whole_width, 3) + + mocked_text.reset_mock() + self.float_prompt._integral_buffer.text = "10" + mocked_text.assert_called() + self.assertEqual(self.float_prompt._integral_width, 3) + self.float_prompt._integral_buffer.text = "100" + self.assertEqual(self.float_prompt._integral_width, 4) From 61ccdf0ef0a0966f0d618dbc3db7561087d634f4 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Mon, 6 Dec 2021 17:17:59 +1100 Subject: [PATCH 26/41] docs: remove s3 demo code --- README.md | 151 -------------------------------------------------- docs/index.md | 10 +--- 2 files changed, 1 insertion(+), 160 deletions(-) diff --git a/README.md b/README.md index 44f7597..a3d99c1 100644 --- a/README.md +++ b/README.md @@ -20,159 +20,8 @@ as well as more customisation options. -↓↓↓ Simple AWS S3 uploader/downloader prompt. - -> Note: [boto3](https://github.com/boto/boto3) package and configured AWS credentials is required for the following example. - ![Demo](https://github.com/kazhala/gif/blob/master/InquirerPy-demo.gif) - - -
- Classic Syntax (PyInquirer) - -```python -import boto3 - -from InquirerPy import prompt -from InquirerPy.exceptions import InvalidArgument -from InquirerPy.validator import PathValidator - -client = boto3.client("s3") - - -def get_bucket(_): - return [bucket["Name"] for bucket in client.list_buckets()["Buckets"]] - - -def walk_s3_bucket(result): - response = [] - paginator = client.get_paginator("list_objects") - for result in paginator.paginate(Bucket=result["bucket"]): - for file in result["Contents"]: - response.append(file["Key"]) - return response - - -def is_upload(result): - return result[0] == "Upload" - - -questions = [ - { - "message": "Select an S3 action:", - "type": "list", - "choices": ["Upload", "Download"], - }, - { - "message": "Enter the filepath to upload:", - "type": "filepath", - "when": is_upload, - "validate": PathValidator(), - "only_files": True, - }, - { - "message": "Select a bucket:", - "type": "fuzzy", - "choices": get_bucket, - "name": "bucket", - }, - { - "message": "Select files to download:", - "type": "fuzzy", - "when": lambda _: not is_upload(_), - "choices": walk_s3_bucket, - "multiselect": True, - }, - { - "message": "Enter destination folder:", - "type": "filepath", - "when": lambda _: not is_upload(_), - "only_directories": True, - "validate": PathValidator(), - }, - {"message": "Confirm?", "type": "confirm", "default": False}, -] - -try: - result = prompt(questions, vi_mode=True) -except InvalidArgument: - print("No available choices") - -# Download or Upload the file based on result ... -``` - -
- -
- Alternative Syntax - -```python -import os - -import boto3 - -from InquirerPy import inquirer -from InquirerPy.exceptions import InvalidArgument -from InquirerPy.validator import PathValidator - -client = boto3.client("s3") -os.environ["INQUIRERPY_VI_MODE"] = "true" - - -def get_bucket(_): - return [bucket["Name"] for bucket in client.list_buckets()["Buckets"]] - - -def walk_s3_bucket(bucket): - response = [] - paginator = client.get_paginator("list_objects") - for result in paginator.paginate(Bucket=bucket): - for file in result["Contents"]: - response.append(file["Key"]) - return response - - -try: - action = inquirer.select( - message="Select an S3 action:", choices=["Upload", "Download"] - ).execute() - - if action == "Upload": - file_to_upload = inquirer.filepath( - message="Enter the filepath to upload:", - validate=PathValidator(), - only_files=True, - ).execute() - bucket = inquirer.fuzzy( - message="Select a bucket:", choices=get_bucket - ).execute() - else: - bucket = inquirer.fuzzy( - message="Select a bucket:", choices=get_bucket - ).execute() - file_to_download = inquirer.fuzzy( - message="Select files to download:", - choices=lambda _: walk_s3_bucket(bucket), - multiselect=True, - ).execute() - destination = inquirer.filepath( - message="Enter destination folder:", - only_directories=True, - validate=PathValidator(), - ).execute() - - confirm = inquirer.confirm(message="Confirm?").execute() -except InvalidArgument: - print("No available choices") - -# Download or Upload the file based on result ... -``` - -
- - - ## Motivation [PyInquirer](https://github.com/CITGuru/PyInquirer) is a great Python port of [Inquirer.js](https://github.com/SBoudrias/Inquirer.js/), however, the project is slowly reaching diff --git a/docs/index.md b/docs/index.md index 0af7756..06231ad 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,17 +5,8 @@ :end-before: ``` -```{Note} -[boto3](https://github.com/boto/boto3) package and configured AWS credentials is required for the following example. -``` - ![Demo](https://assets.kazhala.me/InquirerPy/InquirerPy-demo.gif) -```{include} ../README.md -:start-after: -:end-before: -``` - ## Install ```{admonition} Requirements @@ -153,6 +144,7 @@ python3 examples/classic/rawlist pages/prompts/input.md pages/prompts/secret.md pages/prompts/filepath.md +pages/prompts/number.md pages/prompts/confirm.md pages/prompts/list.md pages/prompts/rawlist.md From bce0f001875e420862347ad52d37938a6db55492 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Mon, 6 Dec 2021 17:55:10 +1100 Subject: [PATCH 27/41] feat(number): enable full vi mode --- InquirerPy/prompts/number.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 8f554c5..fae9b0d 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -256,6 +256,7 @@ def _(_): style=self._style, key_bindings=self._kb, after_render=self._after_render, + editing_mode=self._editing_mode, ) def _on_rendered(self, _) -> None: From 52a51cfd01c9855a3360ebf5b5b49e3fc953989e Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Mon, 6 Dec 2021 17:55:34 +1100 Subject: [PATCH 28/41] feat: enable number prompt for the classic syntax --- InquirerPy/resolver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InquirerPy/resolver.py b/InquirerPy/resolver.py index 4420375..fed19fa 100644 --- a/InquirerPy/resolver.py +++ b/InquirerPy/resolver.py @@ -12,6 +12,7 @@ from InquirerPy.prompts.fuzzy import FuzzyPrompt from InquirerPy.prompts.input import InputPrompt from InquirerPy.prompts.list import ListPrompt +from InquirerPy.prompts.number import NumberPrompt from InquirerPy.prompts.rawlist import RawlistPrompt from InquirerPy.prompts.secret import SecretPrompt from InquirerPy.utils import InquirerPyQuestions, InquirerPySessionResult, get_style @@ -28,10 +29,9 @@ "rawlist": RawlistPrompt, "expand": ExpandPrompt, "fuzzy": FuzzyPrompt, + "number": NumberPrompt, } -list_prompts = {"list", "checkbox", "rawlist", "expand", "fuzzy"} - def prompt( questions: InquirerPyQuestions, From 91ea3c6ebc2af0337e9f8d6003cb75350c06e91f Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Mon, 6 Dec 2021 17:55:57 +1100 Subject: [PATCH 29/41] docs: init number doc --- CHANGELOG.md | 4 + docs/pages/prompts/number.md | 156 +++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 docs/pages/prompts/number.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c1a09..8f41915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Notable changes are documented in this file. - Fixed InvalidArgument raised for callable default +### Added + +- number prompt + ## 0.3.0 (12/10/2021) **New Documentation: [inquirerpy.readthedocs.io](https://inquirerpy.readthedocs.io/en/latest/)** diff --git a/docs/pages/prompts/number.md b/docs/pages/prompts/number.md new file mode 100644 index 0000000..264e990 --- /dev/null +++ b/docs/pages/prompts/number.md @@ -0,0 +1,156 @@ +# number + +A prompt for entering numbers. All non number input will be disabled for this prompt. + +## Example + +## Keybindings + +```{seealso} +{ref}`pages/kb:Keybindings` +``` + +```{include} ../kb.md +:start-after: +:end-before: +``` + +The following dictionary contains the additional keybindings created by this prompt. + +``` +{ + "down": [ + {"key": "down"}, # decrement the number + {"key": "c-n"}, + ], + "up": [ + {"key": "up"}, # increment the number + {"key": "c-p"}, + ], + "left": [ + {"key": "left"}, # move cursor to the left + {"key": "c-b"}, + ], + "right": [ + {"key": "right"}, # move cursor to the right + {"key": "c-f"}, + ], + "focus": [ + {"key": "c-i"}, # focus the alternate input buffer when float_allowed=True + {"key": "s-tab"}, + ], + "negative_toggle": [{"key": "-"}], # toggle result negativity +} +``` + +When `vi_mode` is True, the direction navigation key will be changed. + +```{tip} +Additionally, the input buffer can also enter normal mode by pressing `esc` when `vi_mode` is True. +``` + +``` +{ + "down": [ + {"key": "down"}, + {"key": "j"}, + ], + "up": [ + {"key": "up"}, + {"key": "k"}, + ], + "left": [ + {"key": "left"}, + {"key": "h"}, + ], + "right": [ + {"key": "right"}, + {"key": "l"}, + ], +} +``` + +## Max and Min + +You can set the maximum allowed value as well as the minimum allowed value for the prompt via `max_allowed` and `min_allowed`. + +
+ Classic Syntax (PyInquirer) + +```{code-block} python +from InquirerPy import prompt + +questions = [ + { + "type": "number", + "message": "Number:", + "max_allowed": 10, + "min_allowed": -100 + } +] + +result = prompt(questions) +``` + +
+ +
+ Alternate Syntax + +```{code-block} python +from InquirerPy import inquirer + +result = inquirer.number( + message="Number:", max_allowed=10, min_allowed=-100, +).execute() +``` + +
+ +## Decimal Input + +```{tip} +Once you enable decimal input, the prompt will have a second input buffer. You can keep navigating `left`/`right` +to enter the other input buffer or you can use the `tab`/`shit-tab` to focus the other buffer. +``` + +You can enable decimal input by setting the argument `float_allowed` to True. + +
+ Classic Syntax (PyInquirer) + +```{code-block} python +from InquirerPy import prompt + +questions = [ + { + "type": "number", + "message": "Number:", + "float_allowed": True + } +] + +result = prompt(questions) +``` + +
+ +
+ Alternate Syntax + +```{code-block} python +from InquirerPy import inquirer + +result = inquirer.number( + message="Number:", float_allowed=True, +).execute() +``` + +
+ +## Reference + +```{eval-rst} +.. autoclass:: InquirerPy.prompts.number.NumberPrompt + :noindex: +``` From e6dd3a448e6646a7b195e926938f7c9765b9e871 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Mon, 6 Dec 2021 18:07:17 +1100 Subject: [PATCH 30/41] docs(number): docstring --- InquirerPy/prompts/number.py | 47 +++++++++++++++++++++++++++++++++++- docs/pages/prompts/number.md | 5 ++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index fae9b0d..45daa4a 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -45,7 +45,52 @@ class NumberPrompt(BaseComplexPrompt): - """Create a input prompts that only takes number as input.""" + """Create a input prompts that only takes number as input. + + A wrapper class around :class:`~prompt_toolkit.application.Application`. + + Args: + message: The question to ask the user. + Refer to :ref:`pages/dynamic:message` documentation for more details. + style: An :class:`InquirerPyStyle` instance. + Refer to :ref:`Style ` documentation for more details. + vi_mode: Use vim keybinding for the prompt. + Refer to :ref:`pages/kb:Keybindings` documentation for more details. + default: Set the default value of the prompt. + You can enter either the floating value or integer value as the default. + Refer to :ref:`pages/dynamic:default` documentation for more details. + float_allowed: Allow decimal input. This will change the prompt to have 2 input buffer, one for the + whole value and one for the integral value. + min_allowed: Set the minimum value of the prompt. When the input value goes below this value, it + will automatically reset to this value. + max_allowed: Set the maximum value of the prompt. When the inptu value goes above this value, it + will automatically reset to this value. + qmark: Question mark symbol. Custom symbol that will be displayed infront of the question before its answered. + amark: Answer mark symbol. Custom symbol that will be displayed infront of the question after its answered. + decimal_symbol: Decimal point symbol. Custom symbol to display as the decimal point. + instruction: Short instruction to display next to the question. + long_instruction: Long instructions to display at the bottom of the prompt. + validate: Add validation to user input. + Refer to :ref:`pages/validator:Validator` documentation for more details. + invalid_message: Error message to display when user input is invalid. + Refer to :ref:`pages/validator:Validator` documentation for more details. + invalid_message: Error message to display when user input is invalid. + Refer to :ref:`pages/validator:Validator` documentation for more details. + transformer: A function which performs additional transformation on the value that gets printed to the terminal. + Different than `filter` parameter, this is only visual effect and won’t affect the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`. + Refer to :ref:`pages/dynamic:transformer` documentation for more details. + filter: A function which performs additional transformation on the result. + This affects the actual value returned by :meth:`~InquirerPy.base.simple.BaseSimplePrompt.execute`. + Refer to :ref:`pages/dynamic:filter` documentation for more details. + keybindings: Customise the builtin keybindings. + Refer to :ref:`pages/kb:Keybindings` for more details. + wrap_lines: Soft wrap question lines when question exceeds the terminal width. + raise_keyboard_interrupt: Raise the :class:`KeyboardInterrupt` exception when `ctrl-c` is pressed. If false, the result + will be `None` and the question is skiped. + mandatory: Indicate if the prompt is mandatory. If True, then the question cannot be skipped. + mandatory_message: Error message to show when user attempts to skip mandatory prompt. + session_result: Used internally for :ref:`index:Classic Syntax (PyInquirer)`. + """ def __init__( self, diff --git a/docs/pages/prompts/number.md b/docs/pages/prompts/number.md index 264e990..bf645c7 100644 --- a/docs/pages/prompts/number.md +++ b/docs/pages/prompts/number.md @@ -74,6 +74,11 @@ Additionally, the input buffer can also enter normal mode by pressing `esc` when You can set the maximum allowed value as well as the minimum allowed value for the prompt via `max_allowed` and `min_allowed`. +```{hint} +When the input value goes above/below the max/min value, the input value will automatically reset to the +configured max/min value. +``` +
Classic Syntax (PyInquirer) From 79990a195ae1a854158214040cedab4cdb4ab47e Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 7 Dec 2021 10:18:29 +1100 Subject: [PATCH 31/41] fix(number): empty input --- InquirerPy/prompts/number.py | 12 ++++++++++-- examples/alternate/number.py | 23 +++++++++++++---------- tests/prompts/test_number.py | 22 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 45daa4a..1759806 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -358,9 +358,17 @@ def _handle_right(self, _) -> None: self.focus_buffer.cursor_position += 1 def _handle_enter(self, event) -> None: - result = str(self.value) - if not self._whole_buffer.text and not self._integral_buffer.text: + if not self._float and not self._whole_buffer.text: result = "" + elif ( + self._float + and not self._whole_buffer.text + and not self._integral_buffer.text + ): + result = "" + else: + result = str(self.value) + try: fake_document = FakeDocument(result) self._validator.validate(fake_document) # type: ignore diff --git a/examples/alternate/number.py b/examples/alternate/number.py index 9b31c2a..61f3ed6 100644 --- a/examples/alternate/number.py +++ b/examples/alternate/number.py @@ -1,13 +1,16 @@ from InquirerPy import inquirer from InquirerPy.validator import EmptyInputValidator -result = inquirer.number( - message="hello", - long_instruction="asfasdfasdfa asdfas", - raise_keyboard_interrupt=False, - min_allowed=-10, - max_allowed=10, - default="2.7", - float_allowed=True, - validate=EmptyInputValidator(), -).execute() + +def main() -> None: + result = inquirer.number( + message="Enter a number:", + min_allowed=-2, + max_allowed=10, + validate=EmptyInputValidator(), + vi_mode=True, + ).execute() + + +if __name__ == "__main__": + main() diff --git a/tests/prompts/test_number.py b/tests/prompts/test_number.py index 417777b..404d13b 100644 --- a/tests/prompts/test_number.py +++ b/tests/prompts/test_number.py @@ -164,6 +164,13 @@ def test_handle_enter(self) -> None: self.assertTrue(self.prompt.status["answered"]) self.assertEqual(self.prompt.status["result"], "1") + self.prompt._whole_buffer.text = "" + with patch("prompt_toolkit.utils.Event") as mock: + event = mock.return_value + self.prompt._handle_enter(event) + self.assertTrue(self.prompt.status["answered"]) + self.assertEqual(self.prompt.status["result"], "") + def test_handle_enter_float(self) -> None: self.float_prompt._on_rendered(None) with patch("prompt_toolkit.utils.Event") as mock: @@ -172,6 +179,21 @@ def test_handle_enter_float(self) -> None: self.assertTrue(self.float_prompt.status["answered"]) self.assertEqual(self.float_prompt.status["result"], "1.0") + self.float_prompt._integral_buffer.text = "" + with patch("prompt_toolkit.utils.Event") as mock: + event = mock.return_value + self.float_prompt._handle_enter(event) + self.assertTrue(self.float_prompt.status["answered"]) + self.assertEqual(self.float_prompt.status["result"], "1.0") + + self.float_prompt._integral_buffer.text = "" + self.float_prompt._whole_buffer.text = "" + with patch("prompt_toolkit.utils.Event") as mock: + event = mock.return_value + self.float_prompt._handle_enter(event) + self.assertTrue(self.float_prompt.status["answered"]) + self.assertEqual(self.float_prompt.status["result"], "") + def test_handle_enter_validation(self) -> None: prompt = NumberPrompt(message="", validate=lambda x: x == 1) prompt._on_rendered(None) From 75469710a2a63dfe6c1eab86efdceca9bf44e371 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 7 Dec 2021 11:27:26 +1100 Subject: [PATCH 32/41] refactor(number): use Decimal over float --- InquirerPy/prompts/number.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 1759806..08a6cc5 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -1,5 +1,6 @@ """Module contains the class to create a number prompt.""" import re +from decimal import Decimal from typing import TYPE_CHECKING, Any, Callable, Union, cast from prompt_toolkit.application.application import Application @@ -148,11 +149,11 @@ def __init__( if isinstance(default, Callable): default = cast(Callable, default)(session_result) if self._float: - default = float(cast(int, default)) + default = Decimal(str(float(cast(int, default)))) if self._float: - if not isinstance(default, float): + if not isinstance(default, float) and not isinstance(default, Decimal): raise InvalidArgument( - f"{type(self).__name__} argument 'default' should return type of float" + f"{type(self).__name__} argument 'default' should return type of float or Decimal" ) elif not isinstance(default, int): raise InvalidArgument( @@ -309,9 +310,9 @@ def _on_rendered(self, _) -> None: self._whole_buffer.text = str(self._default) self._integral_buffer.text = "0" else: - self._whole_buffer.text, self._integral_buffer.text = str( - self._default - ).split(".") + whole_buffer_text, integral_buffer_text = str(self._default).split(".") + self._integral_buffer.text = integral_buffer_text + self._whole_buffer.text = whole_buffer_text self._whole_buffer.cursor_position = len(self._whole_buffer.text) self._integral_buffer.cursor_position = 0 @@ -436,7 +437,7 @@ def focus(self, value: Window) -> None: self._layout.focus(self._focus) @property - def value(self) -> Union[int, float]: + def value(self) -> Union[int, float, Decimal]: """Union[int, float]: The actual value of the prompt, combining and transforming all input buffer values.""" try: if not self._float: @@ -447,17 +448,23 @@ def value(self) -> Union[int, float]: self._ending_zero = ending_zero.group(1) else: self._ending_zero = "" - return float(f"{self._whole_buffer.text}.{self._integral_buffer.text}") + return Decimal( + f"{self._whole_buffer.text}.{self._integral_buffer.text if self._integral_buffer.text else 0}" + ) except ValueError: self._set_error(self._value_error_message) return self._default @value.setter - def value(self, value: Union[int, float]) -> None: + def value(self, value: Union[int, float, Decimal]) -> None: if self._min is not None: - value = max(value, self._min if not self._float else float(self._min)) + value = max( + value, self._min if not self._float else Decimal(str(self._min)) + ) if self._max is not None: - value = min(value, self._max if not self._float else float(self._max)) + value = min( + value, self._max if not self._float else Decimal(str(self._max)) + ) if not self._float: self._whole_buffer.text = str(value) else: From 9af2fef76b758ca352a3ba6b472df2e2dca03a73 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 7 Dec 2021 11:27:38 +1100 Subject: [PATCH 33/41] test(number): Decimal --- tests/prompts/test_number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/prompts/test_number.py b/tests/prompts/test_number.py index 404d13b..c337da9 100644 --- a/tests/prompts/test_number.py +++ b/tests/prompts/test_number.py @@ -105,7 +105,7 @@ def test_handle_up_float(self) -> None: self.assertEqual(self.float_prompt._integral_buffer.text, "2") self.float_prompt._handle_focus(None) self.float_prompt._handle_up(None) - self.assertEqual(self.float_prompt._integral_buffer.text, "0") + self.assertEqual(self.float_prompt._integral_buffer.text, "2") def test_handle_left(self) -> None: self.prompt._on_rendered(None) From 7f8c97daf4c8b410b33fa02deb72eb662c00713d Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 7 Dec 2021 11:28:08 +1100 Subject: [PATCH 34/41] chore(examples): added number examples --- examples/alternate/number.py | 10 +++++++--- examples/classic/number.py | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 examples/classic/number.py diff --git a/examples/alternate/number.py b/examples/alternate/number.py index 61f3ed6..58de64b 100644 --- a/examples/alternate/number.py +++ b/examples/alternate/number.py @@ -3,12 +3,16 @@ def main() -> None: - result = inquirer.number( - message="Enter a number:", + integer_val = inquirer.number( + message="Enter an integer:", min_allowed=-2, max_allowed=10, validate=EmptyInputValidator(), - vi_mode=True, + ).execute() + float_val = inquirer.number( + message="Enter a float:", + float_allowed=True, + validate=EmptyInputValidator(), ).execute() diff --git a/examples/classic/number.py b/examples/classic/number.py new file mode 100644 index 0000000..ada0f4c --- /dev/null +++ b/examples/classic/number.py @@ -0,0 +1,26 @@ +from InquirerPy import prompt +from InquirerPy.validator import EmptyInputValidator + + +def main() -> None: + questions = [ + { + "type": "number", + "message": "Enter an integer:", + "min_allowed": -2, + "max_allowed": 10, + "validate": EmptyInputValidator(), + }, + { + "type": "number", + "message": "Enter a float:", + "float_allowed": True, + "validate": EmptyInputValidator(), + }, + ] + + result = prompt(questions) + + +if __name__ == "__main__": + main() From d8b34c06681d3308fcd78348acc059c3705da3c8 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 7 Dec 2021 11:57:00 +1100 Subject: [PATCH 35/41] docs(number): added examples and demo --- docs/pages/prompts/number.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/pages/prompts/number.md b/docs/pages/prompts/number.md index bf645c7..556b82f 100644 --- a/docs/pages/prompts/number.md +++ b/docs/pages/prompts/number.md @@ -4,6 +4,28 @@ A prompt for entering numbers. All non number input will be disabled for this pr ## Example +![demo](https://assets.kazhala.me/InquirerPy/number.gif) + +
+ Classic Syntax (PyInquirer) + +```{eval-rst} +.. literalinclude :: ../../../examples/classic/number.py + :language: python +``` + +
+ +
+ Alternate Syntax + +```{eval-rst} +.. literalinclude :: ../../../examples/alternate/number.py + :language: python +``` + +
+ ## Keybindings ```{seealso} From 4df3fbb847b0795b70450a402b8bff4e5227c105 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 7 Dec 2021 11:57:59 +1100 Subject: [PATCH 36/41] chore(examples): update example wording --- examples/alternate/number.py | 4 ++-- examples/classic/number.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/alternate/number.py b/examples/alternate/number.py index 58de64b..b0a1cff 100644 --- a/examples/alternate/number.py +++ b/examples/alternate/number.py @@ -4,13 +4,13 @@ def main() -> None: integer_val = inquirer.number( - message="Enter an integer:", + message="Enter integer:", min_allowed=-2, max_allowed=10, validate=EmptyInputValidator(), ).execute() float_val = inquirer.number( - message="Enter a float:", + message="Enter float:", float_allowed=True, validate=EmptyInputValidator(), ).execute() diff --git a/examples/classic/number.py b/examples/classic/number.py index ada0f4c..6f7caca 100644 --- a/examples/classic/number.py +++ b/examples/classic/number.py @@ -6,14 +6,14 @@ def main() -> None: questions = [ { "type": "number", - "message": "Enter an integer:", + "message": "Enter integer:", "min_allowed": -2, "max_allowed": 10, "validate": EmptyInputValidator(), }, { "type": "number", - "message": "Enter a float:", + "message": "Enter float:", "float_allowed": True, "validate": EmptyInputValidator(), }, From 318b2bf791ff13d273cd0bc342dbdd2bd7b1aa30 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 7 Dec 2021 12:24:32 +1100 Subject: [PATCH 37/41] feat(number): auto adjust cursor position for negative toggle --- InquirerPy/prompts/number.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index 08a6cc5..cc5af32 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -396,9 +396,17 @@ def _handle_negative_toggle(self, _) -> None: self._whole_buffer.text = "0" return if self._whole_buffer.text.startswith("-"): + move_cursor = self._whole_buffer.cursor_position < len( + self._whole_buffer.text + ) self._whole_buffer.text = self._whole_buffer.text[1:] + if move_cursor: + self._whole_buffer.cursor_position -= 1 else: + move_cursor = self._whole_buffer.cursor_position != 0 self._whole_buffer.text = f"-{self._whole_buffer.text}" + if move_cursor: + self._whole_buffer.cursor_position += 1 def _on_whole_text_change(self, buffer: Buffer) -> None: self._whole_width = len(buffer.text) + 1 From 7fa94323f30188dc8e4fd99c5ff21f8cf6624341 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 7 Dec 2021 12:24:43 +1100 Subject: [PATCH 38/41] test(number): negative toggle --- tests/prompts/test_number.py | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/prompts/test_number.py b/tests/prompts/test_number.py index c337da9..953d116 100644 --- a/tests/prompts/test_number.py +++ b/tests/prompts/test_number.py @@ -236,3 +236,41 @@ def test_on_text_change(self, mocked_text) -> None: self.assertEqual(self.float_prompt._integral_width, 3) self.float_prompt._integral_buffer.text = "100" self.assertEqual(self.float_prompt._integral_width, 4) + + def test_handle_negative_toggle(self) -> None: + self.assertEqual(self.prompt._whole_buffer.text, "1") + self.assertEqual(self.prompt._whole_buffer.cursor_position, 1) + self.prompt._handle_negative_toggle(None) + self.assertEqual(self.prompt._whole_buffer.text, "-1") + self.assertEqual(self.prompt._whole_buffer.cursor_position, 2) + self.prompt._handle_negative_toggle(None) + self.assertEqual(self.prompt._whole_buffer.text, "1") + self.assertEqual(self.prompt._whole_buffer.cursor_position, 1) + + self.prompt._min = -10 + self.prompt._max = 10 + self.prompt._whole_buffer.text = "10" + + self.prompt._whole_buffer.cursor_position = 2 + self.prompt._handle_negative_toggle(None) + self.assertEqual(self.prompt._whole_buffer.text, "-10") + self.assertEqual(self.prompt._whole_buffer.cursor_position, 3) + self.prompt._handle_negative_toggle(None) + self.assertEqual(self.prompt._whole_buffer.text, "10") + self.assertEqual(self.prompt._whole_buffer.cursor_position, 2) + + self.prompt._whole_buffer.cursor_position = 1 + self.prompt._handle_negative_toggle(None) + self.assertEqual(self.prompt._whole_buffer.text, "-10") + self.assertEqual(self.prompt._whole_buffer.cursor_position, 2) + self.prompt._handle_negative_toggle(None) + self.assertEqual(self.prompt._whole_buffer.text, "10") + self.assertEqual(self.prompt._whole_buffer.cursor_position, 1) + + self.prompt._whole_buffer.cursor_position = 0 + self.prompt._handle_negative_toggle(None) + self.assertEqual(self.prompt._whole_buffer.text, "-10") + self.assertEqual(self.prompt._whole_buffer.cursor_position, 1) + self.prompt._handle_negative_toggle(None) + self.assertEqual(self.prompt._whole_buffer.text, "10") + self.assertEqual(self.prompt._whole_buffer.cursor_position, 0) From a5fda504e4ba8d4efce9fdac5dba594eeeca19a2 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Tue, 7 Dec 2021 12:46:54 +1100 Subject: [PATCH 39/41] test(number): more coverage --- tests/prompts/test_number.py | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/prompts/test_number.py b/tests/prompts/test_number.py index 953d116..136cab6 100644 --- a/tests/prompts/test_number.py +++ b/tests/prompts/test_number.py @@ -3,6 +3,7 @@ from prompt_toolkit.keys import Keys +from InquirerPy.exceptions import InvalidArgument from InquirerPy.prompts.number import NumberPrompt @@ -27,6 +28,17 @@ def test_contructor(self) -> None: self.assertFalse(self.prompt._is_float()) self.assertEqual(self.prompt.focus, self.prompt._whole_window) + prompt = NumberPrompt(message="", default=lambda _: 1) + self.assertEqual(prompt._default, 1) + + try: + NumberPrompt(message="", default="asdfasd") + NumberPrompt(message="", default="asdfas", float_allowed=True) + except InvalidArgument: + pass + else: + self.fail("InvalidArgument should be raised") + def test_float_constructor(self) -> None: self.assertTrue(self.float_prompt._float) self.assertEqual(self.float_prompt._default, 1.0) @@ -66,6 +78,10 @@ def test_handle_down(self) -> None: self.prompt._handle_down(None) self.assertEqual(self.prompt._whole_buffer.text, "-2") + self.prompt._whole_buffer.text = "" + self.prompt._handle_down(None) + self.assertEqual(self.prompt._whole_buffer.text, "0") + def test_handle_down_float(self) -> None: self.float_prompt._default = 0.3 self.float_prompt._on_rendered(None) @@ -95,6 +111,10 @@ def test_handle_up(self) -> None: self.prompt._handle_up(None) self.assertEqual(self.prompt._whole_buffer.text, "10") + self.prompt._whole_buffer.text = "" + self.prompt._handle_up(None) + self.assertEqual(self.prompt._whole_buffer.text, "0") + def test_handle_up_float(self) -> None: self.float_prompt._default = 9.0 self.float_prompt._on_rendered(None) @@ -274,3 +294,20 @@ def test_handle_negative_toggle(self) -> None: self.prompt._handle_negative_toggle(None) self.assertEqual(self.prompt._whole_buffer.text, "10") self.assertEqual(self.prompt._whole_buffer.cursor_position, 0) + + self.prompt._whole_buffer.text = "-" + self.prompt._handle_negative_toggle(None) + self.assertEqual(self.prompt._whole_buffer.text, "0") + + def test_cursor_position(self) -> None: + self.prompt._handle_negative_toggle(None) + self.prompt._whole_buffer.cursor_position = 0 + self.assertEqual(self.prompt._whole_buffer.cursor_position, 1) + + def test_value(self) -> None: + self.prompt._whole_buffer.text = "asdfad" + self.assertEqual(self.prompt.value, 1) + self.assertEqual( + self.prompt._get_error_message(), + [("class:validation-toolbar", "Remove any non-integer value")], + ) From 13f0a2e3411b1d2d6e2cc7f4879edeb4022086a0 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Wed, 8 Dec 2021 08:56:35 +1100 Subject: [PATCH 40/41] docs(number): note that its not yet released --- docs/pages/prompts/number.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/pages/prompts/number.md b/docs/pages/prompts/number.md index 556b82f..78cef75 100644 --- a/docs/pages/prompts/number.md +++ b/docs/pages/prompts/number.md @@ -1,5 +1,9 @@ # number +```{attention} +Not yet released as of 0.3.0, it will be included in the next release. +``` + A prompt for entering numbers. All non number input will be disabled for this prompt. ## Example From a306f26047a7039a1b3e276ba119e7b7d47c0bd7 Mon Sep 17 00:00:00 2001 From: Kevin Zhuang Date: Wed, 8 Dec 2021 09:59:23 +1100 Subject: [PATCH 41/41] fix(number): ending zero and adjust cursor position --- InquirerPy/prompts/number.py | 56 ++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/InquirerPy/prompts/number.py b/InquirerPy/prompts/number.py index cc5af32..b27f45a 100644 --- a/InquirerPy/prompts/number.py +++ b/InquirerPy/prompts/number.py @@ -143,8 +143,7 @@ def __init__( self._min = min_allowed self._value_error_message = "Remove any non-integer value" self._decimal_symbol = decimal_symbol - self._ending_zero = "" - self._zero_pattern = re.compile(r"^[0-9]+?(0+)$") + self._leading_zero_pattern = re.compile(r"^(0*)[0-9]+.*") if isinstance(default, Callable): default = cast(Callable, default)(session_result) @@ -316,28 +315,43 @@ def _on_rendered(self, _) -> None: self._whole_buffer.cursor_position = len(self._whole_buffer.text) self._integral_buffer.cursor_position = 0 - def _handle_down(self, _) -> None: + def _handle_number(self, increment: bool) -> None: try: + leading_zeros = "" + if self.focus_buffer == self._integral_buffer: + zeros = self._leading_zero_pattern.match(self._integral_buffer.text) + if zeros is not None: + leading_zeros = zeros.group(1) + current_text_len = len(self.focus_buffer.text) if not self.focus_buffer.text: - self.focus_buffer.text = "0" + next_text = "0" + next_text_len = 1 else: - if ( - self.focus_buffer == self._integral_buffer - and int(self.focus_buffer.text) == 0 - ): - return - self.focus_buffer.text = str(int(self.focus_buffer.text) - 1) + if not increment: + if ( + self.focus_buffer == self._integral_buffer + and int(self.focus_buffer.text) == 0 + ): + return + next_text = leading_zeros + str(int(self.focus_buffer.text) - 1) + else: + next_text = leading_zeros + str(int(self.focus_buffer.text) + 1) + next_text_len = len(next_text) + desired_position = ( + self.focus_buffer.cursor_position + next_text_len - current_text_len + ) + self.focus_buffer.cursor_position = desired_position + self.focus_buffer.text = next_text + if self.focus_buffer.cursor_position != desired_position: + self.focus_buffer.cursor_position = desired_position except ValueError: self._set_error(message=self._value_error_message) + def _handle_down(self, _) -> None: + self._handle_number(increment=False) + def _handle_up(self, _) -> None: - try: - if not self.focus_buffer.text: - self.focus_buffer.text = "0" - else: - self.focus_buffer.text = str(int(self.focus_buffer.text) + 1) - except ValueError: - self._set_error(message=self._value_error_message) + self._handle_number(increment=True) def _handle_left(self, _) -> None: if ( @@ -451,11 +465,6 @@ def value(self) -> Union[int, float, Decimal]: if not self._float: return int(self._whole_buffer.text) else: - ending_zero = self._zero_pattern.match(self._integral_buffer.text) - if ending_zero is not None: - self._ending_zero = ending_zero.group(1) - else: - self._ending_zero = "" return Decimal( f"{self._whole_buffer.text}.{self._integral_buffer.text if self._integral_buffer.text else 0}" ) @@ -476,5 +485,4 @@ def value(self, value: Union[int, float, Decimal]) -> None: if not self._float: self._whole_buffer.text = str(value) else: - self._whole_buffer.text, integral_buffer_text = str(value).split(".") - self._integral_buffer.text = integral_buffer_text + self._ending_zero + self._whole_buffer.text, self._integral_buffer.text = str(value).split(".")