hspylib-clitt 0.9.118__py3-none-any.whl → 0.9.120__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hspylib-clitt might be problematic. Click here for more details.
- build/lib/build/lib/build/lib/clitt/__classpath__.py +28 -0
- build/lib/build/lib/build/lib/clitt/__init__.py +13 -0
- build/lib/build/lib/build/lib/clitt/__main__.py +139 -0
- build/lib/build/lib/build/lib/clitt/addons/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/addons/appman/__init__.py +13 -0
- build/lib/build/lib/build/lib/clitt/addons/appman/appman.py +305 -0
- build/lib/build/lib/build/lib/clitt/addons/appman/appman_enums.py +39 -0
- build/lib/build/lib/build/lib/clitt/addons/appman/templates/__init__.py +11 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/__init__.py +14 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widget.py +70 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widget_entry.py +54 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widgets/__init__.py +14 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widgets/widget_free.py +110 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widgets/widget_punch.py +246 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widgets/widget_send_msg.py +272 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widgets/widget_time_calc.py +146 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widman.py +123 -0
- build/lib/build/lib/build/lib/clitt/core/__init__.py +15 -0
- build/lib/build/lib/build/lib/clitt/core/exception/__init__.py +11 -0
- build/lib/build/lib/build/lib/clitt/core/exception/exceptions.py +19 -0
- build/lib/build/lib/build/lib/clitt/core/icons/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/icons/emojis/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/icons/emojis/emojis.py +41 -0
- build/lib/build/lib/build/lib/clitt/core/icons/emojis/face_smiling.py +40 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/__init__.py +18 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/app_icons.py +55 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/awesome.py +76 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/dashboard_icons.py +93 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/form_icons.py +69 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/game_icons.py +59 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/nav_icons.py +42 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/trickplay_icons.py +39 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/widget_icons.py +37 -0
- build/lib/build/lib/build/lib/clitt/core/preferences.py +87 -0
- build/lib/build/lib/build/lib/clitt/core/term/__init__.py +14 -0
- build/lib/build/lib/build/lib/clitt/core/term/commons.py +106 -0
- build/lib/build/lib/build/lib/clitt/core/term/cursor.py +174 -0
- build/lib/build/lib/build/lib/clitt/core/term/screen.py +106 -0
- build/lib/build/lib/build/lib/clitt/core/term/terminal.py +202 -0
- build/lib/build/lib/build/lib/clitt/core/tui/__init__.py +20 -0
- build/lib/build/lib/build/lib/clitt/core/tui/line_input/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/tui/line_input/keyboard_input.py +229 -0
- build/lib/build/lib/build/lib/clitt/core/tui/line_input/line_input.py +31 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mchoose/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mchoose/mchoose.py +43 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mchoose/menu_choose.py +192 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mdashboard/__init__.py +14 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mdashboard/dashboard_builder.py +54 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mdashboard/dashboard_item.py +31 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mdashboard/mdashboard.py +26 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mdashboard/menu_dashboard.py +148 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/__init__.py +16 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu.py +111 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu_action.py +47 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu_factory.py +117 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu_item.py +196 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu_ui.py +98 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu_view.py +57 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/__init__.py +19 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/access_type.py +26 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/field_builder.py +117 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/form_builder.py +72 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/form_field.py +180 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/input_type.py +30 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/input_validator.py +98 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/menu_input.py +292 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/minput.py +44 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/minput_utils.py +156 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mselect/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mselect/menu_select.py +170 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mselect/mselect.py +36 -0
- build/lib/build/lib/build/lib/clitt/core/tui/table/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/tui/table/table_enums.py +47 -0
- build/lib/build/lib/build/lib/clitt/core/tui/table/table_renderer.py +339 -0
- build/lib/build/lib/build/lib/clitt/core/tui/tui_application.py +52 -0
- build/lib/build/lib/build/lib/clitt/core/tui/tui_component.py +154 -0
- build/lib/build/lib/build/lib/clitt/core/tui/tui_preferences.py +103 -0
- build/lib/build/lib/build/lib/clitt/utils/__init__.py +11 -0
- build/lib/build/lib/build/lib/clitt/utils/git_utils.py +66 -0
- build/lib/build/lib/clitt/__classpath__.py +28 -0
- build/lib/build/lib/clitt/__init__.py +13 -0
- build/lib/build/lib/clitt/__main__.py +139 -0
- build/lib/build/lib/clitt/addons/__init__.py +12 -0
- build/lib/build/lib/clitt/addons/appman/__init__.py +13 -0
- build/lib/build/lib/clitt/addons/appman/appman.py +305 -0
- build/lib/build/lib/clitt/addons/appman/appman_enums.py +39 -0
- build/lib/build/lib/clitt/addons/appman/templates/__init__.py +11 -0
- build/lib/build/lib/clitt/addons/widman/__init__.py +14 -0
- build/lib/build/lib/clitt/addons/widman/widget.py +70 -0
- build/lib/build/lib/clitt/addons/widman/widget_entry.py +54 -0
- build/lib/build/lib/clitt/addons/widman/widgets/__init__.py +14 -0
- build/lib/build/lib/clitt/addons/widman/widgets/widget_free.py +110 -0
- build/lib/build/lib/clitt/addons/widman/widgets/widget_punch.py +246 -0
- build/lib/build/lib/clitt/addons/widman/widgets/widget_send_msg.py +272 -0
- build/lib/build/lib/clitt/addons/widman/widgets/widget_time_calc.py +146 -0
- build/lib/build/lib/clitt/addons/widman/widman.py +123 -0
- build/lib/build/lib/clitt/core/__init__.py +15 -0
- build/lib/build/lib/clitt/core/exception/__init__.py +11 -0
- build/lib/build/lib/clitt/core/exception/exceptions.py +19 -0
- build/lib/build/lib/clitt/core/icons/__init__.py +12 -0
- build/lib/build/lib/clitt/core/icons/emojis/__init__.py +12 -0
- build/lib/build/lib/clitt/core/icons/emojis/emojis.py +41 -0
- build/lib/build/lib/clitt/core/icons/emojis/face_smiling.py +40 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/__init__.py +18 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/app_icons.py +55 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/awesome.py +76 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/dashboard_icons.py +93 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/form_icons.py +69 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/game_icons.py +59 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/nav_icons.py +42 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/trickplay_icons.py +39 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/widget_icons.py +37 -0
- build/lib/build/lib/clitt/core/preferences.py +87 -0
- build/lib/build/lib/clitt/core/term/__init__.py +14 -0
- build/lib/build/lib/clitt/core/term/commons.py +106 -0
- build/lib/build/lib/clitt/core/term/cursor.py +174 -0
- build/lib/build/lib/clitt/core/term/screen.py +106 -0
- build/lib/build/lib/clitt/core/term/terminal.py +202 -0
- build/lib/build/lib/clitt/core/tui/__init__.py +20 -0
- build/lib/build/lib/clitt/core/tui/line_input/__init__.py +12 -0
- build/lib/build/lib/clitt/core/tui/line_input/keyboard_input.py +229 -0
- build/lib/build/lib/clitt/core/tui/line_input/line_input.py +31 -0
- build/lib/build/lib/clitt/core/tui/mchoose/__init__.py +12 -0
- build/lib/build/lib/clitt/core/tui/mchoose/mchoose.py +43 -0
- build/lib/build/lib/clitt/core/tui/mchoose/menu_choose.py +192 -0
- build/lib/build/lib/clitt/core/tui/mdashboard/__init__.py +14 -0
- build/lib/build/lib/clitt/core/tui/mdashboard/dashboard_builder.py +54 -0
- build/lib/build/lib/clitt/core/tui/mdashboard/dashboard_item.py +31 -0
- build/lib/build/lib/clitt/core/tui/mdashboard/mdashboard.py +26 -0
- build/lib/build/lib/clitt/core/tui/mdashboard/menu_dashboard.py +148 -0
- build/lib/build/lib/clitt/core/tui/menu/__init__.py +16 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu.py +111 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu_action.py +47 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu_factory.py +117 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu_item.py +196 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu_ui.py +98 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu_view.py +57 -0
- build/lib/build/lib/clitt/core/tui/minput/__init__.py +19 -0
- build/lib/build/lib/clitt/core/tui/minput/access_type.py +26 -0
- build/lib/build/lib/clitt/core/tui/minput/field_builder.py +117 -0
- build/lib/build/lib/clitt/core/tui/minput/form_builder.py +72 -0
- build/lib/build/lib/clitt/core/tui/minput/form_field.py +180 -0
- build/lib/build/lib/clitt/core/tui/minput/input_type.py +30 -0
- build/lib/build/lib/clitt/core/tui/minput/input_validator.py +98 -0
- build/lib/build/lib/clitt/core/tui/minput/menu_input.py +292 -0
- build/lib/build/lib/clitt/core/tui/minput/minput.py +44 -0
- build/lib/build/lib/clitt/core/tui/minput/minput_utils.py +156 -0
- build/lib/build/lib/clitt/core/tui/mselect/__init__.py +12 -0
- build/lib/build/lib/clitt/core/tui/mselect/menu_select.py +170 -0
- build/lib/build/lib/clitt/core/tui/mselect/mselect.py +36 -0
- build/lib/build/lib/clitt/core/tui/table/__init__.py +12 -0
- build/lib/build/lib/clitt/core/tui/table/table_enums.py +47 -0
- build/lib/build/lib/clitt/core/tui/table/table_renderer.py +339 -0
- build/lib/build/lib/clitt/core/tui/tui_application.py +52 -0
- build/lib/build/lib/clitt/core/tui/tui_component.py +154 -0
- build/lib/build/lib/clitt/core/tui/tui_preferences.py +103 -0
- build/lib/build/lib/clitt/utils/__init__.py +11 -0
- build/lib/build/lib/clitt/utils/git_utils.py +66 -0
- build/lib/clitt/__classpath__.py +28 -0
- build/lib/clitt/__init__.py +13 -0
- build/lib/clitt/__main__.py +139 -0
- build/lib/clitt/addons/__init__.py +12 -0
- build/lib/clitt/addons/appman/__init__.py +13 -0
- build/lib/clitt/addons/appman/appman.py +305 -0
- build/lib/clitt/addons/appman/appman_enums.py +39 -0
- build/lib/clitt/addons/appman/templates/__init__.py +11 -0
- build/lib/clitt/addons/widman/__init__.py +14 -0
- build/lib/clitt/addons/widman/widget.py +70 -0
- build/lib/clitt/addons/widman/widget_entry.py +54 -0
- build/lib/clitt/addons/widman/widgets/__init__.py +14 -0
- build/lib/clitt/addons/widman/widgets/widget_free.py +110 -0
- build/lib/clitt/addons/widman/widgets/widget_punch.py +246 -0
- build/lib/clitt/addons/widman/widgets/widget_send_msg.py +272 -0
- build/lib/clitt/addons/widman/widgets/widget_time_calc.py +146 -0
- build/lib/clitt/addons/widman/widman.py +123 -0
- build/lib/clitt/core/__init__.py +15 -0
- build/lib/clitt/core/exception/__init__.py +11 -0
- build/lib/clitt/core/exception/exceptions.py +19 -0
- build/lib/clitt/core/icons/__init__.py +12 -0
- build/lib/clitt/core/icons/emojis/__init__.py +12 -0
- build/lib/clitt/core/icons/emojis/emojis.py +41 -0
- build/lib/clitt/core/icons/emojis/face_smiling.py +40 -0
- build/lib/clitt/core/icons/font_awesome/__init__.py +18 -0
- build/lib/clitt/core/icons/font_awesome/app_icons.py +55 -0
- build/lib/clitt/core/icons/font_awesome/awesome.py +76 -0
- build/lib/clitt/core/icons/font_awesome/dashboard_icons.py +93 -0
- build/lib/clitt/core/icons/font_awesome/form_icons.py +69 -0
- build/lib/clitt/core/icons/font_awesome/game_icons.py +59 -0
- build/lib/clitt/core/icons/font_awesome/nav_icons.py +42 -0
- build/lib/clitt/core/icons/font_awesome/trickplay_icons.py +39 -0
- build/lib/clitt/core/icons/font_awesome/widget_icons.py +37 -0
- build/lib/clitt/core/preferences.py +87 -0
- build/lib/clitt/core/term/__init__.py +14 -0
- build/lib/clitt/core/term/commons.py +106 -0
- build/lib/clitt/core/term/cursor.py +174 -0
- build/lib/clitt/core/term/screen.py +106 -0
- build/lib/clitt/core/term/terminal.py +202 -0
- build/lib/clitt/core/tui/__init__.py +20 -0
- build/lib/clitt/core/tui/line_input/__init__.py +12 -0
- build/lib/clitt/core/tui/line_input/keyboard_input.py +229 -0
- build/lib/clitt/core/tui/line_input/line_input.py +31 -0
- build/lib/clitt/core/tui/mchoose/__init__.py +12 -0
- build/lib/clitt/core/tui/mchoose/mchoose.py +43 -0
- build/lib/clitt/core/tui/mchoose/menu_choose.py +192 -0
- build/lib/clitt/core/tui/mdashboard/__init__.py +14 -0
- build/lib/clitt/core/tui/mdashboard/dashboard_builder.py +54 -0
- build/lib/clitt/core/tui/mdashboard/dashboard_item.py +31 -0
- build/lib/clitt/core/tui/mdashboard/mdashboard.py +26 -0
- build/lib/clitt/core/tui/mdashboard/menu_dashboard.py +148 -0
- build/lib/clitt/core/tui/menu/__init__.py +16 -0
- build/lib/clitt/core/tui/menu/tui_menu.py +111 -0
- build/lib/clitt/core/tui/menu/tui_menu_action.py +47 -0
- build/lib/clitt/core/tui/menu/tui_menu_factory.py +117 -0
- build/lib/clitt/core/tui/menu/tui_menu_item.py +196 -0
- build/lib/clitt/core/tui/menu/tui_menu_ui.py +98 -0
- build/lib/clitt/core/tui/menu/tui_menu_view.py +57 -0
- build/lib/clitt/core/tui/minput/__init__.py +19 -0
- build/lib/clitt/core/tui/minput/access_type.py +26 -0
- build/lib/clitt/core/tui/minput/field_builder.py +117 -0
- build/lib/clitt/core/tui/minput/form_builder.py +72 -0
- build/lib/clitt/core/tui/minput/form_field.py +180 -0
- build/lib/clitt/core/tui/minput/input_type.py +30 -0
- build/lib/clitt/core/tui/minput/input_validator.py +98 -0
- build/lib/clitt/core/tui/minput/menu_input.py +292 -0
- build/lib/clitt/core/tui/minput/minput.py +44 -0
- build/lib/clitt/core/tui/minput/minput_utils.py +156 -0
- build/lib/clitt/core/tui/mselect/__init__.py +12 -0
- build/lib/clitt/core/tui/mselect/menu_select.py +170 -0
- build/lib/clitt/core/tui/mselect/mselect.py +36 -0
- build/lib/clitt/core/tui/table/__init__.py +12 -0
- build/lib/clitt/core/tui/table/table_enums.py +47 -0
- build/lib/clitt/core/tui/table/table_renderer.py +339 -0
- build/lib/clitt/core/tui/tui_application.py +52 -0
- build/lib/clitt/core/tui/tui_component.py +154 -0
- build/lib/clitt/core/tui/tui_preferences.py +103 -0
- build/lib/clitt/utils/__init__.py +11 -0
- build/lib/clitt/utils/git_utils.py +66 -0
- clitt/.version +1 -1
- clitt/__init__.py +3 -3
- clitt/addons/__init__.py +3 -3
- clitt/addons/appman/__init__.py +3 -3
- clitt/addons/appman/templates/__init__.py +3 -3
- clitt/addons/widman/__init__.py +3 -3
- clitt/addons/widman/widgets/__init__.py +3 -3
- clitt/core/__init__.py +3 -3
- clitt/core/exception/__init__.py +3 -3
- clitt/core/icons/__init__.py +3 -3
- clitt/core/icons/emojis/__init__.py +3 -3
- clitt/core/icons/font_awesome/__init__.py +3 -3
- clitt/core/term/__init__.py +3 -3
- clitt/core/term/commons.py +11 -12
- clitt/core/tui/__init__.py +3 -3
- clitt/core/tui/line_input/__init__.py +3 -3
- clitt/core/tui/line_input/keyboard_input.py +22 -22
- clitt/core/tui/mchoose/__init__.py +3 -3
- clitt/core/tui/mdashboard/__init__.py +3 -3
- clitt/core/tui/menu/__init__.py +3 -3
- clitt/core/tui/minput/__init__.py +3 -3
- clitt/core/tui/mselect/__init__.py +3 -3
- clitt/core/tui/table/__init__.py +3 -3
- clitt/utils/__init__.py +3 -3
- {hspylib_clitt-0.9.118.dist-info → hspylib_clitt-0.9.120.dist-info}/METADATA +2 -2
- hspylib_clitt-0.9.120.dist-info/RECORD +334 -0
- {hspylib_clitt-0.9.118.dist-info → hspylib_clitt-0.9.120.dist-info}/top_level.txt +1 -0
- hspylib_clitt-0.9.118.dist-info/RECORD +0 -97
- {hspylib_clitt-0.9.118.dist-info → hspylib_clitt-0.9.120.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@project: HsPyLib
|
|
6
|
+
@package: clitt.core.tui.minput
|
|
7
|
+
@file: input_validator.py
|
|
8
|
+
@created: Thu, 20 May 2021
|
|
9
|
+
@author: <B>H</B>ugo <B>S</B>aporetti <B>J</B>unior"
|
|
10
|
+
@site: https://github.com/yorevs/hspylib
|
|
11
|
+
@license: MIT - Please refer to <https://opensource.org/licenses/MIT>
|
|
12
|
+
|
|
13
|
+
Copyright·(c)·2024,·HSPyLib
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from hspylib.core.enums.enumeration import Enumeration
|
|
17
|
+
from hspylib.core.tools.validator import Validator
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class InputValidator(Validator):
|
|
23
|
+
"""MenuInput 'input' validator."""
|
|
24
|
+
|
|
25
|
+
class PatternType(Enumeration):
|
|
26
|
+
# fmt: off
|
|
27
|
+
CUSTOM = r'' # It will be set later
|
|
28
|
+
ANYTHING = r'.'
|
|
29
|
+
LETTERS = r'[a-zA-Z]'
|
|
30
|
+
WORDS = r'[a-zA-Z0-9 _]'
|
|
31
|
+
NUMBERS = r'[0-9\.\,]'
|
|
32
|
+
MASKED = r'[a-zA-Z0-9]'
|
|
33
|
+
# fmt: on
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def custom(cls, pattern: str) -> "InputValidator":
|
|
37
|
+
"""Return a custom validator that allows customize the input rules.
|
|
38
|
+
:param pattern: the custom validator pattern.
|
|
39
|
+
"""
|
|
40
|
+
pattern_type = cls.PatternType.CUSTOM
|
|
41
|
+
validator = InputValidator(pattern_type=pattern_type)
|
|
42
|
+
validator.pattern = pattern
|
|
43
|
+
return validator
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def letters(cls) -> "InputValidator":
|
|
47
|
+
"""Return a validator that allows only letters."""
|
|
48
|
+
return InputValidator(cls.PatternType.LETTERS)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def numbers(cls) -> "InputValidator":
|
|
52
|
+
"""Return a validator that allows only numbers."""
|
|
53
|
+
return InputValidator(cls.PatternType.NUMBERS)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def words(cls) -> "InputValidator":
|
|
57
|
+
"""Return a validator that allows only words (space, numbers or letters)."""
|
|
58
|
+
return InputValidator(cls.PatternType.WORDS)
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def anything(cls) -> "InputValidator":
|
|
62
|
+
"""Return a validator that allows any input value."""
|
|
63
|
+
return InputValidator(cls.PatternType.ANYTHING)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def masked(cls) -> "InputValidator":
|
|
67
|
+
"""Return a validator that allows masked inputs."""
|
|
68
|
+
return InputValidator(cls.PatternType.MASKED)
|
|
69
|
+
|
|
70
|
+
def __init__(self, pattern_type: PatternType = PatternType.ANYTHING):
|
|
71
|
+
self._pattern_type = pattern_type
|
|
72
|
+
self._pattern = pattern_type.value
|
|
73
|
+
|
|
74
|
+
def __str__(self) -> str:
|
|
75
|
+
return f'r"{self.pattern}"' if self.pattern_type == self.PatternType.CUSTOM else self.pattern_type.name
|
|
76
|
+
|
|
77
|
+
def __repr__(self):
|
|
78
|
+
return str(self)
|
|
79
|
+
|
|
80
|
+
def __call__(self, *args, **kwargs) -> bool:
|
|
81
|
+
return all(self.validate(value) for value in args)
|
|
82
|
+
|
|
83
|
+
def validate(self, value: str) -> bool:
|
|
84
|
+
"""Validate the value against the validator pattern."""
|
|
85
|
+
unmasked_value = re.split("[|,;]", value)[0] if value else ""
|
|
86
|
+
return bool(re.match(self.pattern, unmasked_value))
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def pattern(self) -> str:
|
|
90
|
+
return str(self._pattern)
|
|
91
|
+
|
|
92
|
+
@pattern.setter
|
|
93
|
+
def pattern(self, pattern: str) -> None:
|
|
94
|
+
self._pattern = rf"{pattern}"
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def pattern_type(self) -> PatternType:
|
|
98
|
+
return self._pattern_type
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@project: HsPyLib
|
|
6
|
+
@package: clitt.core.tui.minput
|
|
7
|
+
@file: menu_input.py
|
|
8
|
+
@created: Wed, 17 May 2023
|
|
9
|
+
@author: <B>H</B>ugo <B>S</B>aporetti <B>J</B>unior"
|
|
10
|
+
@site: https://github.com/yorevs/hspylib
|
|
11
|
+
@license: MIT - Please refer to <https://opensource.org/licenses/MIT>
|
|
12
|
+
|
|
13
|
+
Copyright·(c)·2024,·HSPyLib
|
|
14
|
+
"""
|
|
15
|
+
from clitt.core.icons.font_awesome.form_icons import FormIcons
|
|
16
|
+
from clitt.core.icons.font_awesome.nav_icons import NavIcons
|
|
17
|
+
from clitt.core.term.commons import Direction, get_cursor_position
|
|
18
|
+
from clitt.core.term.terminal import Terminal
|
|
19
|
+
from clitt.core.tui.minput import minput_utils as mu
|
|
20
|
+
from clitt.core.tui.minput.form_builder import FormBuilder
|
|
21
|
+
from clitt.core.tui.minput.form_field import FormField
|
|
22
|
+
from clitt.core.tui.minput.input_type import InputType
|
|
23
|
+
from clitt.core.tui.minput.minput_utils import MASK_SYMBOLS, VALUE_SEPARATORS
|
|
24
|
+
from clitt.core.tui.tui_component import TUIComponent
|
|
25
|
+
from functools import reduce
|
|
26
|
+
from hspylib.core.exception.exceptions import InvalidInputError, InvalidStateError
|
|
27
|
+
from hspylib.core.namespace import Namespace
|
|
28
|
+
from hspylib.core.tools.text_tools import xstr
|
|
29
|
+
from hspylib.modules.cli.keyboard import Keyboard
|
|
30
|
+
from operator import add
|
|
31
|
+
from time import sleep
|
|
32
|
+
from typing import List, Optional
|
|
33
|
+
|
|
34
|
+
import pyperclip
|
|
35
|
+
import re
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MenuInput(TUIComponent):
|
|
39
|
+
"""Provide a form input for terminal UIs."""
|
|
40
|
+
|
|
41
|
+
NAV_ICONS = NavIcons.compose(NavIcons.UP, NavIcons.DOWN)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def builder(cls) -> FormBuilder:
|
|
45
|
+
return FormBuilder()
|
|
46
|
+
|
|
47
|
+
def __init__(self, title: str, fields: List[FormField]):
|
|
48
|
+
super().__init__(title)
|
|
49
|
+
self.fields = fields
|
|
50
|
+
self.positions = [(0, 0) for _ in fields]
|
|
51
|
+
self.cur_field = self._done = None
|
|
52
|
+
self.cur_row = self.cur_col = self.tab_index = 0
|
|
53
|
+
self.max_label_length = max(len(field.label) for field in fields)
|
|
54
|
+
self.max_value_length = max(field.max_length for field in fields)
|
|
55
|
+
self.max_detail_length = max(mu.detail_len(field) for field in fields)
|
|
56
|
+
|
|
57
|
+
def execute(self) -> Optional[Namespace]:
|
|
58
|
+
if len(self.fields) == 0:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
self._prepare_render()
|
|
62
|
+
keypress = self._loop()
|
|
63
|
+
|
|
64
|
+
if keypress == Keyboard.VK_ENTER:
|
|
65
|
+
form_fields = Namespace("FormFields")
|
|
66
|
+
for field in self.fields:
|
|
67
|
+
form_fields.setattr(field.dest, field.value)
|
|
68
|
+
return form_fields
|
|
69
|
+
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
def render(self) -> None:
|
|
73
|
+
|
|
74
|
+
Terminal.set_show_cursor(False)
|
|
75
|
+
self.cursor.restore()
|
|
76
|
+
self.writeln(f"{self.prefs.title_color.placeholder}{self.title}%EOL%%NC%")
|
|
77
|
+
|
|
78
|
+
for idx, field in enumerate(self.fields):
|
|
79
|
+
field_length = field.length
|
|
80
|
+
if self.tab_index == idx:
|
|
81
|
+
mu.mi_print(
|
|
82
|
+
self.screen, f" {field.label}", self.prefs.sel_bg_color.placeholder, self.max_label_length + 2
|
|
83
|
+
)
|
|
84
|
+
self.cur_field = field
|
|
85
|
+
else:
|
|
86
|
+
mu.mi_print(self.screen, f" {field.label}", field_len=self.max_label_length + 2)
|
|
87
|
+
self._buffer_positions(idx, field_length)
|
|
88
|
+
self._render_field(field)
|
|
89
|
+
self._render_details(field, field_length)
|
|
90
|
+
|
|
91
|
+
self.cursor.erase(Direction.DOWN)
|
|
92
|
+
self.draw_navbar(self.navbar())
|
|
93
|
+
self._set_cursor_pos()
|
|
94
|
+
self._re_render = False
|
|
95
|
+
Terminal.set_show_cursor()
|
|
96
|
+
|
|
97
|
+
def navbar(self, **kwargs) -> str:
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
f"%EOL%{NavIcons.POINTER} %GREEN%{self.cur_field.tooltip or f'the {self.cur_field.label.lower()}'}%NC%"
|
|
101
|
+
f"%EOL%{self.prefs.navbar_color.placeholder}%EOL%"
|
|
102
|
+
f"[Enter] Submit [{self.NAV_ICONS}] "
|
|
103
|
+
f"Navigate [{NavIcons.TAB}] Next [Space] Toggle [^P] Paste [Esc] Quit %NC%"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def handle_keypress(self) -> Keyboard:
|
|
107
|
+
length = len(self.fields)
|
|
108
|
+
|
|
109
|
+
if keypress := Keyboard.wait_keystroke():
|
|
110
|
+
match keypress:
|
|
111
|
+
case Keyboard.VK_ESC:
|
|
112
|
+
self._done = True
|
|
113
|
+
case _ as key if key in [Keyboard.VK_TAB, Keyboard.VK_DOWN]:
|
|
114
|
+
self.tab_index = min(length - 1, self.tab_index + 1)
|
|
115
|
+
case _ as key if key in [Keyboard.VK_SHIFT_TAB, Keyboard.VK_UP]:
|
|
116
|
+
self.tab_index = max(0, self.tab_index - 1)
|
|
117
|
+
case Keyboard.VK_BACKSPACE:
|
|
118
|
+
if not self.cur_field.can_write():
|
|
119
|
+
self._display_error("This field is read only !")
|
|
120
|
+
else:
|
|
121
|
+
self._handle_backspace()
|
|
122
|
+
case Keyboard.VK_CTRL_P:
|
|
123
|
+
self._handle_ctrl_p()
|
|
124
|
+
case Keyboard.VK_ENTER:
|
|
125
|
+
return self._handle_enter()
|
|
126
|
+
case _ as key if key.isalnum() or key.ispunct() or key == Keyboard.VK_SPACE:
|
|
127
|
+
if not self.cur_field.can_write():
|
|
128
|
+
self._display_error("This field is read only !")
|
|
129
|
+
else:
|
|
130
|
+
self._handle_input(keypress)
|
|
131
|
+
|
|
132
|
+
self._re_render = True
|
|
133
|
+
|
|
134
|
+
return keypress
|
|
135
|
+
|
|
136
|
+
def _handle_enter(self) -> Optional[Keyboard]:
|
|
137
|
+
"""Handle 'enter' press. Validate & Save form and exit."""
|
|
138
|
+
invalid = next((field for field in self.fields if not field.validate_field()), None)
|
|
139
|
+
if invalid:
|
|
140
|
+
idx = self.fields.index(invalid)
|
|
141
|
+
pos = self.positions[idx]
|
|
142
|
+
self.cur_row = pos[0]
|
|
143
|
+
self.tab_index = idx
|
|
144
|
+
self._display_error("Form field is not valid: " + str(invalid))
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
for idx, field in enumerate(self.fields):
|
|
148
|
+
match field.itype:
|
|
149
|
+
case InputType.MASKED:
|
|
150
|
+
field.value = re.split(VALUE_SEPARATORS, field.value)[0]
|
|
151
|
+
case InputType.CHECKBOX:
|
|
152
|
+
field.value = bool(field.value)
|
|
153
|
+
case InputType.SELECT:
|
|
154
|
+
_, field.value = mu.get_selected(field.value)
|
|
155
|
+
self._done = True
|
|
156
|
+
return Keyboard.VK_ENTER
|
|
157
|
+
|
|
158
|
+
def _handle_input(self, keypress: Keyboard) -> None:
|
|
159
|
+
"""Handle a form input.
|
|
160
|
+
:param keypress: the input provided by the keypress.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
match self.cur_field.itype:
|
|
164
|
+
case InputType.CHECKBOX:
|
|
165
|
+
if keypress == Keyboard.VK_SPACE:
|
|
166
|
+
self.cur_field.value = not self.cur_field.value
|
|
167
|
+
case InputType.SELECT:
|
|
168
|
+
if keypress == Keyboard.VK_SPACE:
|
|
169
|
+
if self.cur_field.value:
|
|
170
|
+
self.cur_field.value = mu.toggle_selected(xstr(self.cur_field.value))
|
|
171
|
+
case InputType.MASKED:
|
|
172
|
+
value, mask = mu.unpack_masked(xstr(self.cur_field.value))
|
|
173
|
+
try:
|
|
174
|
+
self.cur_field.value = mu.append_masked(value, mask, keypress.value)
|
|
175
|
+
except InvalidInputError as err:
|
|
176
|
+
self._display_error(str(err))
|
|
177
|
+
case _:
|
|
178
|
+
if len(xstr(self.cur_field.value)) < self.cur_field.max_length:
|
|
179
|
+
if self.cur_field.validate_input(keypress.value):
|
|
180
|
+
self.cur_field.value = (str(self.cur_field.value) if self.cur_field.value else "") + str(
|
|
181
|
+
keypress.value
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
self._display_error(
|
|
185
|
+
f"Input '{keypress.value}' is invalid. "
|
|
186
|
+
f"This field takes only {self.cur_field.input_validator}!"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def _handle_backspace(self) -> None:
|
|
190
|
+
"""Handle 'backspace' press. Delete previous input."""
|
|
191
|
+
|
|
192
|
+
if self.cur_field.itype == InputType.MASKED:
|
|
193
|
+
value, mask = mu.unpack_masked(str(self.cur_field.value))
|
|
194
|
+
value = value[:-1]
|
|
195
|
+
while mask[len(value) - 1] not in MASK_SYMBOLS:
|
|
196
|
+
value = value[:-1]
|
|
197
|
+
self.cur_field.value = f"{value}|{mask}"
|
|
198
|
+
elif self.cur_field.itype not in [InputType.CHECKBOX, InputType.SELECT]:
|
|
199
|
+
if self.cur_field.can_write() and len(str(self.cur_field.value)) >= 1:
|
|
200
|
+
self.cur_field.value = str(self.cur_field.value)[:-1]
|
|
201
|
+
elif not self.cur_field.can_write():
|
|
202
|
+
self._display_error("This field is read only !")
|
|
203
|
+
|
|
204
|
+
def _handle_ctrl_p(self) -> None:
|
|
205
|
+
"""Handle 'ctrl + p' press. Paste content from clipboard."""
|
|
206
|
+
for c in pyperclip.paste():
|
|
207
|
+
self._handle_input(Keyboard.of_value(c))
|
|
208
|
+
|
|
209
|
+
def _set_cursor_pos(self) -> None:
|
|
210
|
+
"""Set the cursor at the right position according to the ATB index."""
|
|
211
|
+
pos = self.positions[self.tab_index]
|
|
212
|
+
self.screen.cursor.move_to(pos[0], pos[1] + self.cur_field.length)
|
|
213
|
+
|
|
214
|
+
def _render_field(self, field: FormField) -> None:
|
|
215
|
+
"""Render the specified form field.
|
|
216
|
+
:param field: the form field to render.
|
|
217
|
+
"""
|
|
218
|
+
match field.itype:
|
|
219
|
+
case InputType.TEXT:
|
|
220
|
+
mu.mi_print(self.screen, xstr(field.value), field_len=self.max_value_length)
|
|
221
|
+
case InputType.PASSWORD:
|
|
222
|
+
mu.mi_print(self.screen, "*" * field.length, field_len=self.max_value_length)
|
|
223
|
+
case InputType.CHECKBOX:
|
|
224
|
+
mu.mi_print(
|
|
225
|
+
self.screen,
|
|
226
|
+
" ",
|
|
227
|
+
FormIcons.CHECK_SQUARE.unicode if field.value else FormIcons.UNCHECK_SQUARE.unicode,
|
|
228
|
+
field_len=self.max_value_length - 1,
|
|
229
|
+
)
|
|
230
|
+
case InputType.SELECT:
|
|
231
|
+
if field.value:
|
|
232
|
+
mat = re.search(rf".*({VALUE_SEPARATORS})?<(.+)>({VALUE_SEPARATORS})?.*", str(field.value))
|
|
233
|
+
sel_value = mat.group(2) if mat else re.split(VALUE_SEPARATORS, str(field.value))[0]
|
|
234
|
+
mu.mi_print(self.screen, f"{sel_value}", field_len=self.max_value_length)
|
|
235
|
+
case InputType.MASKED:
|
|
236
|
+
value, mask = mu.unpack_masked(str(field.value))
|
|
237
|
+
mu.mi_print(self.screen, mu.over_masked(value, mask), field_len=self.max_value_length)
|
|
238
|
+
case _:
|
|
239
|
+
raise InvalidStateError(f"Invalid form field type: {field.itype}")
|
|
240
|
+
|
|
241
|
+
def _render_details(self, field: FormField, field_details: int) -> None:
|
|
242
|
+
"""Render details about total/remaining field characters.
|
|
243
|
+
:param field: the form field to render.
|
|
244
|
+
:param field_details: details about the form field (total / remaining) size.
|
|
245
|
+
"""
|
|
246
|
+
padding = 1 - len(str(self.max_detail_length / 2))
|
|
247
|
+
fmt = "{:<3}{:>" + str(padding) + "}/{:<" + str(padding) + "} %NC%"
|
|
248
|
+
if field.itype == InputType.SELECT:
|
|
249
|
+
idx, _ = mu.get_selected(field.value)
|
|
250
|
+
count = len(re.split(VALUE_SEPARATORS, field.value))
|
|
251
|
+
self.writeln(fmt.format(field.icon, idx + 1 if idx >= 0 else 1, count))
|
|
252
|
+
elif field.itype == InputType.MASKED:
|
|
253
|
+
value, mask = mu.unpack_masked(xstr(field.value))
|
|
254
|
+
self.writeln(
|
|
255
|
+
fmt.format(
|
|
256
|
+
field.icon,
|
|
257
|
+
reduce(add, list(map(mask[: len(value)].count, MASK_SYMBOLS))),
|
|
258
|
+
reduce(add, list(map(mask.count, MASK_SYMBOLS))),
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
else:
|
|
262
|
+
self.writeln(fmt.format(field.icon, field_details, field.max_length))
|
|
263
|
+
|
|
264
|
+
def _buffer_positions(self, field_index: int, field_size: int) -> None:
|
|
265
|
+
"""Buffer all cursor positions to avoid calling get_cursor_pos over and over because it is a very
|
|
266
|
+
expensive call.
|
|
267
|
+
:param field_index: the current form field index.
|
|
268
|
+
:param field_size: the form field length.
|
|
269
|
+
"""
|
|
270
|
+
if f_pos := get_cursor_position() if self.positions[field_index] == (0, 0) else self.positions[field_index]:
|
|
271
|
+
self.positions[field_index] = f_pos
|
|
272
|
+
if self.tab_index == field_index:
|
|
273
|
+
self.cur_row, self.cur_col = f_pos[0], f_pos[1] + field_size
|
|
274
|
+
|
|
275
|
+
def _display_error(self, err_msg: str) -> None:
|
|
276
|
+
"""Display a form filling or submitting error.
|
|
277
|
+
:param err_msg: the error message.
|
|
278
|
+
"""
|
|
279
|
+
Terminal.set_enable_echo(False)
|
|
280
|
+
Terminal.set_show_cursor(False)
|
|
281
|
+
offset = 15 # Sum of minput used characters that are not related to the field.
|
|
282
|
+
err_pos = offset + self.max_label_length + self.max_value_length + self.max_detail_length
|
|
283
|
+
self.cursor.move_to(self.cur_row, err_pos)
|
|
284
|
+
mu.mi_print_err(self.screen, f"{FormIcons.ARROW_LEFT} {err_msg}")
|
|
285
|
+
# This calculation gives a good delay amount based on the size of the message.
|
|
286
|
+
sleep(max(2.0, int(len(err_msg) / 21)))
|
|
287
|
+
Terminal.set_enable_echo()
|
|
288
|
+
# Erase the message after the timeout
|
|
289
|
+
self.cursor.move_to(self.cur_row, err_pos)
|
|
290
|
+
self.cursor.erase(Direction.RIGHT)
|
|
291
|
+
Terminal.set_show_cursor()
|
|
292
|
+
self._re_render = True
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@project: HsPyLib
|
|
6
|
+
@package: clitt.core.tui.minput
|
|
7
|
+
@file: minput.py
|
|
8
|
+
@created: Tue, 4 May 2021
|
|
9
|
+
@author: <B>H</B>ugo <B>S</B>aporetti <B>J</B>unior"
|
|
10
|
+
@site: https://github.com/yorevs/hspylib
|
|
11
|
+
@license: MIT - Please refer to <https://opensource.org/licenses/MIT>
|
|
12
|
+
|
|
13
|
+
Copyright·(c)·2024,·HSPyLib
|
|
14
|
+
"""
|
|
15
|
+
from clitt.core.tui.minput.form_field import FormField
|
|
16
|
+
from clitt.core.tui.minput.menu_input import MenuInput
|
|
17
|
+
from hspylib.core.enums.charset import Charset
|
|
18
|
+
from hspylib.core.namespace import Namespace
|
|
19
|
+
from hspylib.core.preconditions import check_argument
|
|
20
|
+
from hspylib.core.tools.text_tools import quote, snakecase
|
|
21
|
+
from typing import List, Optional
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def minput(
|
|
27
|
+
form_fields: List[FormField], title: str = "Please fill all fields of the form fields below", output: str = None
|
|
28
|
+
) -> Optional[Namespace]:
|
|
29
|
+
"""
|
|
30
|
+
Terminal UI menu form input method.
|
|
31
|
+
:param form_fields: the provided form items to input from.
|
|
32
|
+
:param title: the title to be displayed before the form.
|
|
33
|
+
:param output: optional output file containing the marked items.
|
|
34
|
+
:return: a namespace containing all form values.
|
|
35
|
+
"""
|
|
36
|
+
check_argument(len(form_fields) > 0, "Must provide at least one form field!")
|
|
37
|
+
result = MenuInput(title, form_fields).execute()
|
|
38
|
+
|
|
39
|
+
if result and output:
|
|
40
|
+
with open(output, "w", encoding=Charset.UTF_8.val) as f_out:
|
|
41
|
+
for name, value in zip(result.attributes, result.values):
|
|
42
|
+
f_out.write(f"{snakecase(name, screaming=True)}={quote(value)}" + os.linesep)
|
|
43
|
+
|
|
44
|
+
return result
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@project: HsPyLib
|
|
6
|
+
@package: clitt.core.tui.minput
|
|
7
|
+
@file: minput_utils.py
|
|
8
|
+
@created: Thu, 20 May 2021
|
|
9
|
+
@author: <B>H</B>ugo <B>S</B>aporetti <B>J</B>unior"
|
|
10
|
+
@site: https://github.com/yorevs/hspylib
|
|
11
|
+
@license: MIT - Please refer to <https://opensource.org/licenses/MIT>
|
|
12
|
+
|
|
13
|
+
Copyright·(c)·2024,·HSPyLib
|
|
14
|
+
"""
|
|
15
|
+
from clitt.core.term.screen import Screen
|
|
16
|
+
from hspylib.core.exception.exceptions import InvalidInputError
|
|
17
|
+
from typing import Any, Optional, Tuple
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
MASK_SYMBOLS = ["#", "@", "%", "*"]
|
|
22
|
+
|
|
23
|
+
VALUE_SEPARATORS = r"[|,;]"
|
|
24
|
+
|
|
25
|
+
VALUE_SELECTORS = r"[<>]"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def detail_len(field: Any) -> int:
|
|
29
|
+
"""Return the length of the details, according to the form field.
|
|
30
|
+
:param field: the form field to get details.
|
|
31
|
+
"""
|
|
32
|
+
max_len = len(str(field.max_length))
|
|
33
|
+
return 1 + (2 * max_len)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def mi_print(screen: Screen, text: str, prefix: str = None, field_len: int = 0, end: str = "") -> None:
|
|
37
|
+
"""Special menu input print.
|
|
38
|
+
:param screen: the component's screen.
|
|
39
|
+
:param text: the text to be printed.
|
|
40
|
+
:param prefix the text to print before the text.
|
|
41
|
+
:param field_len: the length of the field.
|
|
42
|
+
:param end: string appended after the last value, default an empty string.
|
|
43
|
+
"""
|
|
44
|
+
fmt = ("{}" if prefix else "") + "{:<" + str(field_len) + "} : "
|
|
45
|
+
if prefix:
|
|
46
|
+
screen.cursor.write(fmt.format(prefix, text), end=end)
|
|
47
|
+
else:
|
|
48
|
+
screen.cursor.write(fmt.format(text or ""), end=end)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def mi_print_err(screen: Screen, text: str) -> None:
|
|
52
|
+
"""Special menu input print error message.
|
|
53
|
+
:param screen: the component's screen.
|
|
54
|
+
:param text: the text to be printed.
|
|
55
|
+
"""
|
|
56
|
+
screen.cursor.write(f"%RED%{text}%NC%")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def toggle_selected(tokenized_values: str) -> str:
|
|
60
|
+
"""Toggle the 'select' component value. Selects the next value on the token sequence.
|
|
61
|
+
:param tokenized_values: the 'select' component tokenized values.
|
|
62
|
+
"""
|
|
63
|
+
values = re.split(VALUE_SEPARATORS, tokenized_values)
|
|
64
|
+
cur_idx = next((idx for idx, val in enumerate(values) if val.find("<") >= 0), -1)
|
|
65
|
+
if cur_idx < 0:
|
|
66
|
+
if len(values) > 1:
|
|
67
|
+
values[1] = f"<{values[1]}>"
|
|
68
|
+
else:
|
|
69
|
+
values[0] = f"<{values[0]}>"
|
|
70
|
+
return "|".join(values)
|
|
71
|
+
unselected = list(map(lambda x: re.sub(VALUE_SELECTORS, "", x), values))
|
|
72
|
+
# fmt: off
|
|
73
|
+
return '|'.join([
|
|
74
|
+
f'<{val}>'
|
|
75
|
+
if idx == (cur_idx + 1) or ((cur_idx + 1) >= len(unselected) and idx == 0)
|
|
76
|
+
else val for idx, val in enumerate(unselected)
|
|
77
|
+
])
|
|
78
|
+
# fmt: on
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_selected(tokenized_values: str) -> Optional[Tuple[int, str]]:
|
|
82
|
+
"""Get the selected value from the 'select' component tokens.
|
|
83
|
+
:param tokenized_values: the 'select' component tokenized values.
|
|
84
|
+
"""
|
|
85
|
+
values = re.split(VALUE_SEPARATORS, tokenized_values)
|
|
86
|
+
# fmt: off
|
|
87
|
+
sel_item = next((
|
|
88
|
+
re.sub(VALUE_SELECTORS, '', val)
|
|
89
|
+
for val in values if val.startswith('<') and val.endswith('>')
|
|
90
|
+
), values[0])
|
|
91
|
+
# fmt: on
|
|
92
|
+
try:
|
|
93
|
+
return values.index(sel_item), sel_item
|
|
94
|
+
except ValueError:
|
|
95
|
+
try:
|
|
96
|
+
return values.index(f"<{sel_item}>"), sel_item
|
|
97
|
+
except ValueError:
|
|
98
|
+
return -1, sel_item
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def unpack_masked(value: str) -> Tuple[str, str]:
|
|
102
|
+
"""Unpack all values of the 'masked' component.
|
|
103
|
+
:param value: the masked values to unpack.
|
|
104
|
+
"""
|
|
105
|
+
if value:
|
|
106
|
+
parts = re.split(VALUE_SEPARATORS, value)
|
|
107
|
+
return parts[0], parts[1] if len(parts) == 2 else ""
|
|
108
|
+
|
|
109
|
+
return "", ""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def append_masked(value: str, mask: str, keypress_value: chr) -> str:
|
|
113
|
+
"""Append a value to the masked field.
|
|
114
|
+
:param value: the masked field current value.
|
|
115
|
+
:param mask: the masked field's mask.
|
|
116
|
+
:param keypress_value: the value to append (it can be a part of the mask itself).
|
|
117
|
+
"""
|
|
118
|
+
idx = len(value)
|
|
119
|
+
masked_value = value
|
|
120
|
+
if idx < len(mask):
|
|
121
|
+
if keypress_value == mask[idx]:
|
|
122
|
+
return f"{value}{keypress_value}|{mask}"
|
|
123
|
+
while idx < len(mask) and mask[idx] not in MASK_SYMBOLS:
|
|
124
|
+
masked_value += mask[idx]
|
|
125
|
+
idx += 1
|
|
126
|
+
if mask and idx < len(mask):
|
|
127
|
+
mask_re = mask_regex(mask, idx)
|
|
128
|
+
if re.search(mask_re, keypress_value):
|
|
129
|
+
masked_value += keypress_value
|
|
130
|
+
else:
|
|
131
|
+
raise InvalidInputError(f"Value '{keypress_value}' is invalid. Expecting: {mask_re}")
|
|
132
|
+
|
|
133
|
+
return f"{masked_value}|{mask}"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def over_masked(value: str, mask: str) -> str:
|
|
137
|
+
"""
|
|
138
|
+
Return the value to be printed by a 'masked' input field. A placeholder value will be used for unfilled values.
|
|
139
|
+
:param value: the masked field current value.
|
|
140
|
+
:param mask: the masked field's mask.
|
|
141
|
+
"""
|
|
142
|
+
if value:
|
|
143
|
+
over_masked_val = ""
|
|
144
|
+
for idx, element in enumerate(mask):
|
|
145
|
+
if element in MASK_SYMBOLS:
|
|
146
|
+
over_masked_val += value[idx] if idx < len(value) else mask[idx]
|
|
147
|
+
else:
|
|
148
|
+
over_masked_val += element
|
|
149
|
+
return over_masked_val
|
|
150
|
+
|
|
151
|
+
return mask
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def mask_regex(mask: str, idx: int) -> str:
|
|
155
|
+
"""Return a regex matching the index of the mask."""
|
|
156
|
+
return mask[idx].replace("#", "[0-9]").replace("@", "[a-zA-Z]").replace("%", "[a-zA-Z0-9]").replace("*", ".")
|