hspylib-clitt 0.9.32__py3-none-any.whl → 0.9.34__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.

clitt/.version CHANGED
@@ -1 +1 @@
1
- 0.9.32
1
+ 0.9.34
clitt/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt
6
6
  """Package initialization."""
@@ -9,4 +9,4 @@ __all__ = [
9
9
  'addons',
10
10
  'core'
11
11
  ]
12
- __version__ = '0.9.32'
12
+ __version__ = '0.9.34'
clitt/addons/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.addons
6
6
  """Package initialization."""
@@ -10,4 +10,4 @@ __all__ = [
10
10
  'setman',
11
11
  'widman'
12
12
  ]
13
- __version__ = '0.9.32'
13
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.addons.appman
6
6
  """Package initialization."""
@@ -10,4 +10,4 @@ __all__ = [
10
10
  'appman_enums',
11
11
  'templates'
12
12
  ]
13
- __version__ = '0.9.32'
13
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.addons.appman.templates
6
6
  """Package initialization."""
@@ -8,4 +8,4 @@
8
8
  __all__ = [
9
9
 
10
10
  ]
11
- __version__ = '0.9.32'
11
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.addons.setman
6
6
  """Package initialization."""
@@ -13,4 +13,4 @@ __all__ = [
13
13
  'setman_repository',
14
14
  'setman_service'
15
15
  ]
16
- __version__ = '0.9.32'
16
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.addons.widman
6
6
  """Package initialization."""
@@ -11,4 +11,4 @@ __all__ = [
11
11
  'widgets',
12
12
  'widman'
13
13
  ]
14
- __version__ = '0.9.32'
14
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.addons.widman.widgets
6
6
  """Package initialization."""
@@ -11,4 +11,4 @@ __all__ = [
11
11
  'widget_send_msg',
12
12
  'widget_time_calc'
13
13
  ]
14
- __version__ = '0.9.32'
14
+ __version__ = '0.9.34'
clitt/core/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.core
6
6
  """Package initialization."""
@@ -9,4 +9,4 @@ __all__ = [
9
9
  'icons',
10
10
  'tui'
11
11
  ]
12
- __version__ = '0.9.32'
12
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.core.icons
6
6
  """Package initialization."""
@@ -9,4 +9,4 @@ __all__ = [
9
9
  'emojis',
10
10
  'font_awesome'
11
11
  ]
12
- __version__ = '0.9.32'
12
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.core.icons.emojis
6
6
  """Package initialization."""
@@ -9,4 +9,4 @@ __all__ = [
9
9
  'emojis',
10
10
  'face_smiling'
11
11
  ]
12
- __version__ = '0.9.32'
12
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.core.icons.font_awesome
6
6
  """Package initialization."""
@@ -14,4 +14,4 @@ __all__ = [
14
14
  'nav_icons',
15
15
  'widget_icons'
16
16
  ]
17
- __version__ = '0.9.32'
17
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.core.tui
6
6
  """Package initialization."""
@@ -17,4 +17,4 @@ __all__ = [
17
17
  'tui_preferences',
18
18
  'tui_screen'
19
19
  ]
20
- __version__ = '0.9.32'
20
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.core.tui.mchoose
6
6
  """Package initialization."""
@@ -9,4 +9,4 @@ __all__ = [
9
9
  'mchoose',
10
10
  'menu_choose'
11
11
  ]
12
- __version__ = '0.9.32'
12
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.core.tui.mdashboard
6
6
  """Package initialization."""
@@ -11,4 +11,4 @@ __all__ = [
11
11
  'mdashboard',
12
12
  'menu_dashboard'
13
13
  ]
14
- __version__ = '0.9.32'
14
+ __version__ = '0.9.34'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.core.tui.menu
6
6
  """Package initialization."""
@@ -13,4 +13,4 @@ __all__ = [
13
13
  'tui_menu_ui',
14
14
  'tui_menu_view'
15
15
  ]
16
- __version__ = '0.9.32'
16
+ __version__ = '0.9.34'
@@ -1,12 +1,13 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.32
3
+ # hspylib-clitt v0.9.34
4
4
  #
5
5
  # Package: main.clitt.core.tui.minput
6
6
  """Package initialization."""
7
7
 
8
8
  __all__ = [
9
9
  'access_type',
10
+ 'field_builder',
10
11
  'form_builder',
11
12
  'form_field',
12
13
  'input_type',
@@ -15,4 +16,4 @@ __all__ = [
15
16
  'minput',
16
17
  'minput_utils'
17
18
  ]
18
- __version__ = '0.9.32'
19
+ __version__ = '0.9.34'
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @project: HsPyLib
6
+ @package: clitt.core.tui.minput
7
+ @file: field_builder.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 2023, HsPyLib team
14
+ """
15
+ import re
16
+ from functools import reduce
17
+ from typing import Any
18
+
19
+ from hspylib.core.preconditions import check_argument
20
+ from hspylib.core.tools.commons import str_to_bool
21
+ from hspylib.core.tools.text_tools import snakecase
22
+
23
+ from clitt.core.tui.minput.access_type import AccessType
24
+ from clitt.core.tui.minput.form_field import FIELD_VALIDATOR_FNC, FormField
25
+ from clitt.core.tui.minput.input_type import InputType
26
+ from clitt.core.tui.minput.input_validator import InputValidator
27
+ from clitt.core.tui.minput.minput_utils import get_selected, MASK_SYMBOLS, unpack_masked, VALUE_SEPARATORS
28
+
29
+
30
+ class FieldBuilder:
31
+ """MenuInput form field builder."""
32
+
33
+ @staticmethod
34
+ def _validate_field(field: FormField) -> bool:
35
+ match field.itype:
36
+ case InputType.MASKED:
37
+ value, mask = unpack_masked(field.value)
38
+ valid = reduce(lambda n, m: n + m, list(map(lambda s: mask[len(value):].count(s), MASK_SYMBOLS))) == 0
39
+ case InputType.CHECKBOX:
40
+ valid = isinstance(field.value, bool)
41
+ case InputType.SELECT:
42
+ _, value = get_selected(field.value)
43
+ length = len(str(value or ''))
44
+ valid = field.min_length <= length <= field.max_length
45
+ case _:
46
+ length = len(str(field.value or ''))
47
+ valid = field.min_length <= length <= field.max_length
48
+ return valid
49
+
50
+ def __init__(self, parent: Any):
51
+ self._parent = parent
52
+ self._label = "Label"
53
+ self._dest = None
54
+ self._itype = InputType.TEXT
55
+ self._access_type = AccessType.READ_WRITE
56
+ self._min_max_length = 5, 30
57
+ self._value = ""
58
+ self._validator = InputValidator.anything(), self._validate_field
59
+
60
+ def label(self, label: str) -> "FieldBuilder":
61
+ self._label = label
62
+ return self
63
+
64
+ def dest(self, dest: str) -> "FieldBuilder":
65
+ self._dest = dest
66
+ return self
67
+
68
+ def itype(self, itype: str) -> "FieldBuilder":
69
+ self._itype = InputType.of_value(itype)
70
+ return self
71
+
72
+ def min_max_length(self, min_length: int, max_length: int) -> "FieldBuilder":
73
+ check_argument(max_length >= min_length, "Invalid field length: ({}-{})", min_length, max_length)
74
+ check_argument(
75
+ max_length > 0 and min_length > 0, "Invalid field length: ({}-{})", min_length, max_length
76
+ )
77
+ self._min_max_length = min_length, max_length
78
+ return self
79
+
80
+ def access_type(self, access_type: str) -> "FieldBuilder":
81
+ self._access_type = AccessType.of_value(access_type)
82
+ return self
83
+
84
+ def value(self, value: Any | None) -> "FieldBuilder":
85
+ self._value = value
86
+ return self
87
+
88
+ def validator(
89
+ self,
90
+ input_validator: InputValidator = None,
91
+ field_validator: FIELD_VALIDATOR_FNC = None) -> "FieldBuilder":
92
+ self._validator = input_validator, field_validator or self._validate_field
93
+ return self
94
+
95
+ def build(self) -> Any:
96
+ if self._itype == InputType.CHECKBOX:
97
+ self._value = str_to_bool(str(self._value))
98
+ elif self._itype == InputType.SELECT:
99
+ parts = re.split(VALUE_SEPARATORS, re.sub('[<>]', '', str(self._value or '')))
100
+ self._min_max_length = len(min(parts, key=len)), len(max(parts, key=len))
101
+ elif self._itype == InputType.MASKED:
102
+ _, mask = unpack_masked(str(self._value))
103
+ min_max = reduce(lambda n, m: n + m, list(map(lambda s: mask.count(s), MASK_SYMBOLS)))
104
+ self._min_max_length = min_max, min_max
105
+ self._dest = self._dest or snakecase(self._label)
106
+ self._parent.fields.append(FormField(
107
+ self._label, self._dest, self._itype,
108
+ self._min_max_length[0], self._min_max_length[1],
109
+ self._access_type, self._value, self._validator[0], self._validator[1]
110
+ ))
111
+
112
+ return self._parent
@@ -15,104 +15,25 @@
15
15
  import re
16
16
  from typing import Any, List
17
17
 
18
- from hspylib.core.preconditions import check_argument
19
- from hspylib.core.tools.commons import str_to_bool
20
18
  from hspylib.core.tools.dict_tools import get_or_default
21
- from hspylib.core.tools.text_tools import snakecase
22
19
 
23
- from clitt.core.tui.minput.access_type import AccessType
20
+ from clitt.core.tui.minput.field_builder import FieldBuilder
24
21
  from clitt.core.tui.minput.form_field import FormField
25
- from clitt.core.tui.minput.input_type import InputType
26
22
  from clitt.core.tui.minput.input_validator import InputValidator
27
- from clitt.core.tui.minput.minput_utils import unpack_masked
28
23
 
29
24
 
30
25
  class FormBuilder:
31
26
  """MenuInput form builder."""
32
27
 
33
- @staticmethod
34
- def or_else_get(options: tuple | list, index: int, default_value: Any = None) -> Any:
35
- """TODO"""
36
- return get_or_default(options, index, default_value) or default_value
37
-
38
- class FieldBuilder:
39
- """MenuInput form field builder."""
40
-
41
- def __init__(self, parent: Any):
42
- self.parent = parent
43
- self.field = FormField()
44
-
45
- def label(self, label: str) -> "FormBuilder.FieldBuilder":
46
- self.field.label = label
47
- return self
48
-
49
- def dest(self, dest: str) -> "FormBuilder.FieldBuilder":
50
- self.field.dest = dest
51
- return self
52
-
53
- def itype(self, itype: str) -> "FormBuilder.FieldBuilder":
54
- self.field.itype = InputType.of_value(itype)
55
- return self
56
-
57
- def validator(self, validator: InputValidator) -> "FormBuilder.FieldBuilder":
58
- self.field.input_validator = validator
59
- return self
60
-
61
- def min_max_length(self, min_length: int, max_length: int) -> "FormBuilder.FieldBuilder":
62
- check_argument(max_length >= min_length, "Not a valid field length: ({}-{})", min_length, max_length)
63
- check_argument(
64
- max_length > 0 and min_length > 0, "Not a valid field length: ({}-{})", min_length, max_length
65
- )
66
- self.field.min_length = min_length
67
- self.field.max_length = max_length
68
- return self
69
-
70
- def access_type(self, access_type: str) -> "FormBuilder.FieldBuilder":
71
- self.field.access_type = AccessType.of_value(access_type)
72
- return self
73
-
74
- def value(self, value: Any | None) -> "FormBuilder.FieldBuilder":
75
- check_argument(
76
- not value or self.field.assign(value, True),
77
- 'Not a valid value: "{}". Validation pattern="{}"',
78
- value,
79
- self.field.input_validator,
80
- )
81
- return self
82
-
83
- def build(self) -> Any:
84
- self.field.itype = self.field.itype or InputType.TEXT
85
- if self.field.itype == InputType.CHECKBOX:
86
- self.field.value = "1" if str_to_bool(str(self.field.value)) else "0"
87
- self.field.min_length = self.field.max_length = 1
88
- self.validator(InputValidator.custom(r"[01]"))
89
- elif self.field.itype == InputType.SELECT:
90
- self.field.min_length = self.field.max_length = 1
91
- self.validator(InputValidator.anything())
92
- elif self.field.itype == InputType.MASKED:
93
- _, mask = unpack_masked(self.field.value)
94
- self.field.min_length = self.field.max_length = len(mask)
95
- self.validator(
96
- InputValidator.custom(
97
- mask
98
- .replace("#", "[0-9]")
99
- .replace("@", "[a-zA-Z]")
100
- .replace("%", "[a-zA-Z0-9]")
101
- .replace("*", ".")
102
- ))
103
- self.field.label = self.field.label or "Field"
104
- self.field.dest = self.field.dest or f"{snakecase(self.field.label)}"
105
- self.field.min_length = self.field.min_length or 1
106
- self.field.max_length = self.field.max_length or 30
107
- self.field.access_type = self.field.access_type or AccessType.READ_WRITE
108
- self.field.value = self.field.value if self.field.value else ""
109
- self.parent.fields.append(self.field)
110
- return self.parent
111
-
112
28
  def __init__(self) -> None:
113
29
  self.fields = []
30
+ self._fn_validate = None
31
+
32
+ @staticmethod
33
+ def get_attr(parts: List[str], index: int, default_value: Any = None) -> str:
34
+ return get_or_default(parts, index, default_value) or default_value
114
35
 
115
- def from_tokenized(self, tokenized_fields: List[str]) -> 'FormBuilder':
36
+ def from_tokenized(self, minput_tokens: List[str], separator: str = '|') -> 'FormBuilder':
116
37
  """Construct the forms based on string tokens.
117
38
 
118
39
  Field tokens (in-order):
@@ -123,30 +44,30 @@ class FormBuilder:
123
44
  [Perm] : The field permissions. One of {r|[rw]}. Where \"r\" for Read Only ; \"rw\" for Read & Write.
124
45
  [Value] : The initial value of the field. This field may not be blank if the field is read only.
125
46
  """
126
- for tk_field in tokenized_fields:
127
- parts = list(map(str.strip, tk_field.split('|')))
128
- validator_fn = getattr(InputValidator, self.or_else_get(parts, 2, 'anything'))
129
- min_max = list(map(int, map(str.strip, self.or_else_get(parts, 3, '1/30').split('/'))))
130
- access = re.sub('^rw$', 'read-write', self.or_else_get(parts, 4, 'rw'))
47
+ for tk_field in minput_tokens:
48
+ parts = list(map(str.strip, tk_field.split(separator)))
49
+ validator_fn = getattr(InputValidator, self.get_attr(parts, 2, 'anything'))
50
+ min_max = list(map(int, map(str.strip, self.get_attr(parts, 3, '5/30').split('/'))))
51
+ access = re.sub('^rw$', 'read-write', self.get_attr(parts, 4, 'rw'))
131
52
  access = re.sub('^r$', 'read-only', access)
132
53
  # fmt: off
133
54
  self.field() \
134
- .label(self.or_else_get(parts, 0, 'Label')) \
135
- .itype(self.or_else_get(parts, 1, 'text')) \
55
+ .label(self.get_attr(parts, 0, 'Label')) \
56
+ .itype(self.get_attr(parts, 1, 'text')) \
136
57
  .min_max_length(
137
- self.or_else_get(min_max, 0, 1),
138
- self.or_else_get(min_max, 1, 30)
58
+ get_or_default(min_max, 0, 5),
59
+ get_or_default(min_max, 1, 30)
139
60
  ) \
140
61
  .access_type(access) \
141
62
  .validator(validator_fn()) \
142
- .value(self.or_else_get(parts, 5, None)) \
63
+ .value(self.get_attr(parts, 5, None)) \
143
64
  .build()
144
65
  # fmt: on
145
66
 
146
67
  return self
147
68
 
148
69
  def field(self) -> Any:
149
- return FormBuilder.FieldBuilder(self)
70
+ return FieldBuilder(self)
150
71
 
151
- def build(self) -> list:
72
+ def build(self) -> List[FormField]:
152
73
  return self.fields
@@ -12,12 +12,17 @@
12
12
 
13
13
  Copyright 2023, HsPyLib team
14
14
  """
15
+ from typing import Any, Callable, Optional, TypeVar
16
+
17
+ from hspylib.core.exception.exceptions import InvalidInputError
15
18
 
16
19
  from clitt.core.icons.font_awesome.form_icons import FormIcons
17
20
  from clitt.core.tui.minput.access_type import AccessType
18
21
  from clitt.core.tui.minput.input_type import InputType
19
22
  from clitt.core.tui.minput.input_validator import InputValidator
20
- from typing import Any
23
+ from clitt.core.tui.minput.minput_utils import get_selected, MASK_SYMBOLS, toggle_selected, unpack_masked
24
+
25
+ FIELD_VALIDATOR_FNC = TypeVar("FIELD_VALIDATOR_FNC", bound=Callable[['FormField'], bool])
21
26
 
22
27
 
23
28
  class FormField:
@@ -28,30 +33,81 @@ class FormField:
28
33
  label: str = None,
29
34
  dest: str = None,
30
35
  itype: InputType = InputType.TEXT,
31
- min_length: int = 0,
36
+ min_length: int = 5,
32
37
  max_length: int = 30,
33
38
  access_type: AccessType = AccessType.READ_WRITE,
34
39
  value: Any = "",
35
- input_validator: InputValidator = None,
40
+ input_validator: InputValidator = InputValidator.anything(),
41
+ field_validator: FIELD_VALIDATOR_FNC = None
36
42
  ):
37
- self.label = label
38
- self.dest = dest
39
- self.itype = itype
40
- self.min_length = min_length
41
- self.max_length = max_length
42
- self.access_type = access_type
43
- self.value = value
44
- self.input_validator = input_validator or InputValidator.anything(min_length, max_length)
43
+ self._label = label
44
+ self._dest = dest
45
+ self._itype = itype
46
+ self._min_length = min_length
47
+ self._max_length = max_length
48
+ self._access_type = access_type
49
+ self.input_validator = input_validator
50
+ self.field_validator = field_validator
51
+ self._value = self.assign(value)
45
52
 
46
53
  def __str__(self) -> str:
47
- return f"{self.label}: {self.itype}({self.min_length}-{self.max_length}) [{self.access_type}] = '{self.value}'"
54
+ return (
55
+ f"[{self.label}, {self.itype.name}({self.min_length}/{self.max_length})"
56
+ f"<{self.access_type}> = '{self.value or ''}']"
57
+ )
48
58
 
49
59
  def __repr__(self):
50
60
  return str(self)
51
61
 
52
62
  @property
53
- def width(self) -> int:
54
- return len(str(self.value)) if self.itype != InputType.SELECT else 1
63
+ def label(self) -> str:
64
+ return self._label
65
+
66
+ @property
67
+ def dest(self) -> str:
68
+ return self._dest
69
+
70
+ @property
71
+ def itype(self) -> InputType:
72
+ return self._itype
73
+
74
+ @property
75
+ def min_length(self) -> int:
76
+ return self._min_length
77
+
78
+ @property
79
+ def max_length(self) -> int:
80
+ return self._max_length
81
+
82
+ @property
83
+ def access_type(self) -> AccessType:
84
+ return self._access_type
85
+
86
+ @property
87
+ def value(self) -> Optional[Any]:
88
+ return self._value
89
+
90
+ @value.setter
91
+ def value(self, new_value: Any) -> None:
92
+ self._value = self.assign(new_value)
93
+
94
+ @property
95
+ def length(self) -> int:
96
+ """Get the field real length, depending on the field type."""
97
+ real_value = str(self.value or '')
98
+ match self.itype:
99
+ case InputType.CHECKBOX:
100
+ real_value = '1'
101
+ case InputType.SELECT:
102
+ _, real_value = get_selected(str(self.value))
103
+ case InputType.MASKED:
104
+ real_value, mask = unpack_masked(str(self.value))
105
+ idx = len(real_value)
106
+ while idx < len(mask) and mask[idx] not in MASK_SYMBOLS:
107
+ idx += 1
108
+ return idx
109
+
110
+ return len(real_value)
55
111
 
56
112
  @property
57
113
  def icon(self) -> FormIcons:
@@ -83,13 +139,36 @@ class FormField:
83
139
  """Whether this field value can be set or not."""
84
140
  return self.access_type == AccessType.READ_WRITE
85
141
 
86
- def assign(self, value: Any, skip_validation: bool = False) -> bool:
87
- """Assign a value for this field.Must match the input validator, otherwise an exception will be thrown."""
88
- if skip_validation or self.validate_input(value):
89
- self.value = value
90
- return True
91
- return False
142
+ def assign(self, value: Any) -> Any:
143
+ """Assign a value for this field. Must match the input validator, otherwise an exception will be thrown.
144
+ :param value: TODO
145
+ """
146
+ valid = True
147
+ if value is not None and self.input_validator:
148
+ match self.itype:
149
+ case InputType.MASKED:
150
+ unpack_masked(value)
151
+ case InputType.SELECT:
152
+ toggle_selected(value)
153
+ case InputType.CHECKBOX:
154
+ valid = isinstance(value, bool)
155
+ case _:
156
+ valid = all([self.validate_input(val) for val in str(value)])
157
+
158
+ if not valid:
159
+ raise InvalidInputError(f"Value {value} is invalid!")
160
+
161
+ self._value = value
162
+
163
+ return self._value
92
164
 
93
165
  def validate_input(self, value: Any = None) -> bool:
94
- """Validate the input using the assigned validator."""
95
- return self.input_validator.validate(str(value) or str(self.value)) if self.input_validator else False
166
+ """Validate the input using the assigned validator.
167
+ :param value: the value to validate against.
168
+ """
169
+ return self.input_validator(str(value)) if self.input_validator else True
170
+
171
+ def validate_field(self) -> bool:
172
+ """Validate the field using the assigned validator function.
173
+ """
174
+ return self.field_validator(self) if self.field_validator else False