click-extended 0.4.0__py3-none-any.whl → 1.0.1__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.
- click_extended/__init__.py +10 -6
- click_extended/classes.py +12 -8
- click_extended/core/__init__.py +10 -0
- click_extended/core/decorators/__init__.py +21 -0
- click_extended/core/decorators/argument.py +227 -0
- click_extended/core/decorators/command.py +93 -0
- click_extended/core/decorators/env.py +155 -0
- click_extended/core/decorators/group.py +96 -0
- click_extended/core/decorators/option.py +347 -0
- click_extended/core/decorators/prompt.py +69 -0
- click_extended/core/decorators/selection.py +155 -0
- click_extended/core/decorators/tag.py +109 -0
- click_extended/core/nodes/__init__.py +21 -0
- click_extended/core/nodes/_root_node.py +1012 -0
- click_extended/core/nodes/argument_node.py +165 -0
- click_extended/core/nodes/child_node.py +555 -0
- click_extended/core/nodes/child_validation_node.py +100 -0
- click_extended/core/nodes/node.py +55 -0
- click_extended/core/nodes/option_node.py +205 -0
- click_extended/core/nodes/parent_node.py +220 -0
- click_extended/core/nodes/validation_node.py +124 -0
- click_extended/core/other/__init__.py +7 -0
- click_extended/core/other/_click_command.py +60 -0
- click_extended/core/other/_click_group.py +246 -0
- click_extended/core/other/_tree.py +491 -0
- click_extended/core/other/context.py +496 -0
- click_extended/decorators/__init__.py +29 -0
- click_extended/decorators/check/__init__.py +57 -0
- click_extended/decorators/check/conflicts.py +149 -0
- click_extended/decorators/check/contains.py +69 -0
- click_extended/decorators/check/dependencies.py +115 -0
- click_extended/decorators/check/divisible_by.py +48 -0
- click_extended/decorators/check/ends_with.py +85 -0
- click_extended/decorators/check/exclusive.py +75 -0
- click_extended/decorators/check/falsy.py +37 -0
- click_extended/decorators/check/is_email.py +43 -0
- click_extended/decorators/check/is_hex_color.py +41 -0
- click_extended/decorators/check/is_hostname.py +47 -0
- click_extended/decorators/check/is_ipv4.py +46 -0
- click_extended/decorators/check/is_ipv6.py +46 -0
- click_extended/decorators/check/is_json.py +40 -0
- click_extended/decorators/check/is_mac_address.py +40 -0
- click_extended/decorators/check/is_negative.py +37 -0
- click_extended/decorators/check/is_non_zero.py +37 -0
- click_extended/decorators/check/is_port.py +39 -0
- click_extended/decorators/check/is_positive.py +37 -0
- click_extended/decorators/check/is_url.py +75 -0
- click_extended/decorators/check/is_uuid.py +40 -0
- click_extended/decorators/check/length.py +68 -0
- click_extended/decorators/check/not_empty.py +49 -0
- click_extended/decorators/check/regex.py +47 -0
- click_extended/decorators/check/requires.py +190 -0
- click_extended/decorators/check/starts_with.py +87 -0
- click_extended/decorators/check/truthy.py +37 -0
- click_extended/decorators/compare/__init__.py +15 -0
- click_extended/decorators/compare/at_least.py +57 -0
- click_extended/decorators/compare/at_most.py +57 -0
- click_extended/decorators/compare/between.py +119 -0
- click_extended/decorators/compare/greater_than.py +183 -0
- click_extended/decorators/compare/less_than.py +183 -0
- click_extended/decorators/convert/__init__.py +31 -0
- click_extended/decorators/convert/convert_angle.py +94 -0
- click_extended/decorators/convert/convert_area.py +123 -0
- click_extended/decorators/convert/convert_bits.py +211 -0
- click_extended/decorators/convert/convert_distance.py +154 -0
- click_extended/decorators/convert/convert_energy.py +155 -0
- click_extended/decorators/convert/convert_power.py +128 -0
- click_extended/decorators/convert/convert_pressure.py +131 -0
- click_extended/decorators/convert/convert_speed.py +122 -0
- click_extended/decorators/convert/convert_temperature.py +89 -0
- click_extended/decorators/convert/convert_time.py +108 -0
- click_extended/decorators/convert/convert_volume.py +218 -0
- click_extended/decorators/convert/convert_weight.py +158 -0
- click_extended/decorators/load/__init__.py +13 -0
- click_extended/decorators/load/load_csv.py +117 -0
- click_extended/decorators/load/load_json.py +61 -0
- click_extended/decorators/load/load_toml.py +47 -0
- click_extended/decorators/load/load_yaml.py +72 -0
- click_extended/decorators/math/__init__.py +37 -0
- click_extended/decorators/math/absolute.py +35 -0
- click_extended/decorators/math/add.py +48 -0
- click_extended/decorators/math/ceil.py +36 -0
- click_extended/decorators/math/clamp.py +51 -0
- click_extended/decorators/math/divide.py +42 -0
- click_extended/decorators/math/floor.py +36 -0
- click_extended/decorators/math/maximum.py +39 -0
- click_extended/decorators/math/minimum.py +39 -0
- click_extended/decorators/math/modulo.py +39 -0
- click_extended/decorators/math/multiply.py +51 -0
- click_extended/decorators/math/normalize.py +76 -0
- click_extended/decorators/math/power.py +39 -0
- click_extended/decorators/math/rounded.py +39 -0
- click_extended/decorators/math/sqrt.py +39 -0
- click_extended/decorators/math/subtract.py +39 -0
- click_extended/decorators/math/to_percent.py +63 -0
- click_extended/decorators/misc/__init__.py +17 -0
- click_extended/decorators/misc/choice.py +139 -0
- click_extended/decorators/misc/confirm_if.py +147 -0
- click_extended/decorators/misc/default.py +95 -0
- click_extended/decorators/misc/deprecated.py +131 -0
- click_extended/decorators/misc/experimental.py +79 -0
- click_extended/decorators/misc/now.py +42 -0
- click_extended/decorators/random/__init__.py +21 -0
- click_extended/decorators/random/random_bool.py +49 -0
- click_extended/decorators/random/random_choice.py +63 -0
- click_extended/decorators/random/random_datetime.py +140 -0
- click_extended/decorators/random/random_float.py +62 -0
- click_extended/decorators/random/random_integer.py +56 -0
- click_extended/decorators/random/random_prime.py +196 -0
- click_extended/decorators/random/random_string.py +77 -0
- click_extended/decorators/random/random_uuid.py +119 -0
- click_extended/decorators/transform/__init__.py +71 -0
- click_extended/decorators/transform/add_prefix.py +58 -0
- click_extended/decorators/transform/add_suffix.py +58 -0
- click_extended/decorators/transform/apply.py +35 -0
- click_extended/decorators/transform/basename.py +44 -0
- click_extended/decorators/transform/dirname.py +44 -0
- click_extended/decorators/transform/expand_vars.py +36 -0
- click_extended/decorators/transform/remove_prefix.py +57 -0
- click_extended/decorators/transform/remove_suffix.py +57 -0
- click_extended/decorators/transform/replace.py +46 -0
- click_extended/decorators/transform/slugify.py +45 -0
- click_extended/decorators/transform/split.py +43 -0
- click_extended/decorators/transform/strip.py +148 -0
- click_extended/decorators/transform/to_case.py +216 -0
- click_extended/decorators/transform/to_date.py +75 -0
- click_extended/decorators/transform/to_datetime.py +83 -0
- click_extended/decorators/transform/to_path.py +274 -0
- click_extended/decorators/transform/to_time.py +77 -0
- click_extended/decorators/transform/to_timestamp.py +114 -0
- click_extended/decorators/transform/truncate.py +47 -0
- click_extended/types.py +1 -1
- click_extended/utils/__init__.py +13 -0
- click_extended/utils/casing.py +169 -0
- click_extended/utils/checks.py +48 -0
- click_extended/utils/dispatch.py +1016 -0
- click_extended/utils/format.py +101 -0
- click_extended/utils/humanize.py +209 -0
- click_extended/utils/naming.py +238 -0
- click_extended/utils/process.py +294 -0
- click_extended/utils/selection.py +267 -0
- click_extended/utils/time.py +46 -0
- {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/METADATA +100 -29
- click_extended-1.0.1.dist-info/RECORD +149 -0
- click_extended-0.4.0.dist-info/RECORD +0 -10
- {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/WHEEL +0 -0
- {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/licenses/AUTHORS.md +0 -0
- {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Class to process strings and transform to different casings."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import unicodedata
|
|
5
|
+
|
|
6
|
+
NON_ALPHABETIC_PATTERN = re.compile(r"[^A-Za-z]+")
|
|
7
|
+
NON_ALPHANUMERIC_PATTERN = re.compile(r"[^\w]+", re.UNICODE)
|
|
8
|
+
LOWER_TO_UPPER_PATTERN = re.compile(r"([a-z\d])([A-Z])")
|
|
9
|
+
UPPER_TO_UPPER_LOWER_PATTERN = re.compile(r"([A-Z]+)([A-Z][a-z])")
|
|
10
|
+
NUMBER_TO_LETTER_PATTERN = re.compile(r"(\d)([^\W\d_])", re.UNICODE)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Casing:
|
|
14
|
+
"""Class to process strings and transform to different casings."""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def _normalize_unicode(value: str) -> str:
|
|
18
|
+
"""Normalize Unicode characters to ASCII equivalents."""
|
|
19
|
+
nfd = unicodedata.normalize("NFD", value)
|
|
20
|
+
return nfd.encode("ascii", "ignore").decode("ascii")
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def _split_into_words(value: str) -> list[str]:
|
|
24
|
+
"""
|
|
25
|
+
Split a string into words, handling various
|
|
26
|
+
delimiters and case transitions.
|
|
27
|
+
"""
|
|
28
|
+
if not value:
|
|
29
|
+
return []
|
|
30
|
+
|
|
31
|
+
value = value.strip()
|
|
32
|
+
value = value.replace("_", " ")
|
|
33
|
+
value = re.sub(r"([a-z\d])([A-Z](?=[a-z]))", r"\1 \2", value)
|
|
34
|
+
value = UPPER_TO_UPPER_LOWER_PATTERN.sub(r"\1 \2", value)
|
|
35
|
+
value = re.sub(r"(\d)([A-Za-z])", r"\1 \2", value)
|
|
36
|
+
value = re.sub(r"[^\w]+", " ", value, flags=re.UNICODE)
|
|
37
|
+
|
|
38
|
+
words = [word for word in value.split() if word]
|
|
39
|
+
|
|
40
|
+
normalized_words: list[str] = []
|
|
41
|
+
for word in words:
|
|
42
|
+
normalized = Casing._normalize_unicode(word)
|
|
43
|
+
if normalized:
|
|
44
|
+
normalized_words.append(normalized)
|
|
45
|
+
else:
|
|
46
|
+
normalized_words.append(word)
|
|
47
|
+
|
|
48
|
+
return normalized_words
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _split_into_words_preserve_case(value: str) -> list[str]:
|
|
52
|
+
"""
|
|
53
|
+
Split a string into words while preserving
|
|
54
|
+
original capitalization.
|
|
55
|
+
"""
|
|
56
|
+
if not value:
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
value = value.strip()
|
|
60
|
+
value = value.replace("_", " ")
|
|
61
|
+
value = re.sub(r"([a-z\d])([A-Z](?=[a-z]))", r"\1 \2", value)
|
|
62
|
+
value = UPPER_TO_UPPER_LOWER_PATTERN.sub(r"\1 \2", value)
|
|
63
|
+
value = re.sub(r"(\d)([A-Za-z])", r"\1 \2", value)
|
|
64
|
+
value = re.sub(r"[^\w]+", " ", value, flags=re.UNICODE)
|
|
65
|
+
|
|
66
|
+
return [word for word in value.split() if word]
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def to_lower_case(value: str) -> str:
|
|
70
|
+
"""Convert the value to lower case."""
|
|
71
|
+
value = value.strip()
|
|
72
|
+
value = re.sub(r"[\[\]]", "", value).strip()
|
|
73
|
+
value = value.replace("\t", " ")
|
|
74
|
+
return value.lower()
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def to_upper_case(value: str) -> str:
|
|
78
|
+
"""Convert the value to upper case."""
|
|
79
|
+
value = value.strip()
|
|
80
|
+
value = re.sub(r"[\[\]]", "", value).strip()
|
|
81
|
+
value = value.replace("\t", " ")
|
|
82
|
+
return value.upper()
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def to_meme_case(value: str) -> str:
|
|
86
|
+
"""Convert the value to mEmE cAsE."""
|
|
87
|
+
if not value:
|
|
88
|
+
return ""
|
|
89
|
+
|
|
90
|
+
value = value.strip()
|
|
91
|
+
|
|
92
|
+
result: list[str] = []
|
|
93
|
+
char_index = 0
|
|
94
|
+
|
|
95
|
+
for char in value:
|
|
96
|
+
if char.isalpha():
|
|
97
|
+
if char_index % 2 == 0:
|
|
98
|
+
result.append(char.lower())
|
|
99
|
+
else:
|
|
100
|
+
result.append(char.upper())
|
|
101
|
+
char_index += 1
|
|
102
|
+
else:
|
|
103
|
+
result.append(char)
|
|
104
|
+
|
|
105
|
+
return "".join(result)
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def to_snake_case(value: str) -> str:
|
|
109
|
+
"""Convert the value to snake_case."""
|
|
110
|
+
words = Casing._split_into_words(value)
|
|
111
|
+
return "_".join(word.lower() for word in words)
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def to_screaming_snake_case(value: str) -> str:
|
|
115
|
+
"""Convert the value to SCREAMING_SNAKE_CASE."""
|
|
116
|
+
words = Casing._split_into_words(value)
|
|
117
|
+
return "_".join(word.upper() for word in words)
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def to_camel_case(value: str) -> str:
|
|
121
|
+
"""Convert the value to camelCase."""
|
|
122
|
+
words = Casing._split_into_words(value)
|
|
123
|
+
if not words:
|
|
124
|
+
return ""
|
|
125
|
+
return words[0].lower() + "".join(
|
|
126
|
+
word.capitalize() for word in words[1:]
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def to_pascal_case(value: str) -> str:
|
|
131
|
+
"""Convert the value to PascalCase."""
|
|
132
|
+
words = Casing._split_into_words(value)
|
|
133
|
+
return "".join(word.capitalize() for word in words)
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def to_kebab_case(value: str) -> str:
|
|
137
|
+
"""Convert the value to kebab-case."""
|
|
138
|
+
words = Casing._split_into_words(value)
|
|
139
|
+
return "-".join(word.lower() for word in words)
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def to_train_case(value: str) -> str:
|
|
143
|
+
"""Convert the value to Train-Case."""
|
|
144
|
+
words = Casing._split_into_words(value)
|
|
145
|
+
return "-".join(word.capitalize() for word in words)
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def to_flat_case(value: str) -> str:
|
|
149
|
+
"""Convert the value to flatcase."""
|
|
150
|
+
words = Casing._split_into_words(value)
|
|
151
|
+
return "".join(word.lower() for word in words)
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def to_dot_case(value: str) -> str:
|
|
155
|
+
"""Convert the value to dot.case."""
|
|
156
|
+
words = Casing._split_into_words(value)
|
|
157
|
+
return ".".join(word.lower() for word in words)
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def to_title_case(value: str) -> str:
|
|
161
|
+
"""Convert the value to Title Case."""
|
|
162
|
+
words = Casing._split_into_words(value)
|
|
163
|
+
return " ".join(word.capitalize() for word in words)
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def to_path_case(value: str) -> str:
|
|
167
|
+
"""Convert the value to path/case."""
|
|
168
|
+
words = Casing._split_into_words(value)
|
|
169
|
+
return "/".join(word.lower() for word in words)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Various checking utilities."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, TypeGuard
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from click_extended.core.decorators.argument import Argument
|
|
7
|
+
from click_extended.core.decorators.option import Option
|
|
8
|
+
from click_extended.core.decorators.tag import Tag
|
|
9
|
+
from click_extended.core.nodes.node import Node
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_option(node: "Node") -> TypeGuard["Option"]:
|
|
13
|
+
"""
|
|
14
|
+
Check if a node is an `Option` instance.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
TypeGuard:
|
|
18
|
+
`True` if the parent is an `Option` instance, `False` otherwise.
|
|
19
|
+
"""
|
|
20
|
+
from click_extended.core.decorators.option import Option
|
|
21
|
+
|
|
22
|
+
return isinstance(node, Option)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_argument(node: "Node") -> TypeGuard["Argument"]:
|
|
26
|
+
"""
|
|
27
|
+
Check if a node is an `Argument` instance.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
TypeGuard:
|
|
31
|
+
`True` if the parent is an `Argument` instance, `False` otherwise.
|
|
32
|
+
"""
|
|
33
|
+
from click_extended.core.decorators.argument import Argument
|
|
34
|
+
|
|
35
|
+
return isinstance(node, Argument)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def is_tag(node: "Node") -> TypeGuard["Tag"]:
|
|
39
|
+
"""
|
|
40
|
+
Check if a node is an `Tag` instance.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
TypeGuard:
|
|
44
|
+
`True` if the parent is an `Tag` instance, `False` otherwise.
|
|
45
|
+
"""
|
|
46
|
+
from click_extended.core.decorators.tag import Tag
|
|
47
|
+
|
|
48
|
+
return isinstance(node, Tag)
|