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.
Files changed (149) hide show
  1. click_extended/__init__.py +10 -6
  2. click_extended/classes.py +12 -8
  3. click_extended/core/__init__.py +10 -0
  4. click_extended/core/decorators/__init__.py +21 -0
  5. click_extended/core/decorators/argument.py +227 -0
  6. click_extended/core/decorators/command.py +93 -0
  7. click_extended/core/decorators/env.py +155 -0
  8. click_extended/core/decorators/group.py +96 -0
  9. click_extended/core/decorators/option.py +347 -0
  10. click_extended/core/decorators/prompt.py +69 -0
  11. click_extended/core/decorators/selection.py +155 -0
  12. click_extended/core/decorators/tag.py +109 -0
  13. click_extended/core/nodes/__init__.py +21 -0
  14. click_extended/core/nodes/_root_node.py +1012 -0
  15. click_extended/core/nodes/argument_node.py +165 -0
  16. click_extended/core/nodes/child_node.py +555 -0
  17. click_extended/core/nodes/child_validation_node.py +100 -0
  18. click_extended/core/nodes/node.py +55 -0
  19. click_extended/core/nodes/option_node.py +205 -0
  20. click_extended/core/nodes/parent_node.py +220 -0
  21. click_extended/core/nodes/validation_node.py +124 -0
  22. click_extended/core/other/__init__.py +7 -0
  23. click_extended/core/other/_click_command.py +60 -0
  24. click_extended/core/other/_click_group.py +246 -0
  25. click_extended/core/other/_tree.py +491 -0
  26. click_extended/core/other/context.py +496 -0
  27. click_extended/decorators/__init__.py +29 -0
  28. click_extended/decorators/check/__init__.py +57 -0
  29. click_extended/decorators/check/conflicts.py +149 -0
  30. click_extended/decorators/check/contains.py +69 -0
  31. click_extended/decorators/check/dependencies.py +115 -0
  32. click_extended/decorators/check/divisible_by.py +48 -0
  33. click_extended/decorators/check/ends_with.py +85 -0
  34. click_extended/decorators/check/exclusive.py +75 -0
  35. click_extended/decorators/check/falsy.py +37 -0
  36. click_extended/decorators/check/is_email.py +43 -0
  37. click_extended/decorators/check/is_hex_color.py +41 -0
  38. click_extended/decorators/check/is_hostname.py +47 -0
  39. click_extended/decorators/check/is_ipv4.py +46 -0
  40. click_extended/decorators/check/is_ipv6.py +46 -0
  41. click_extended/decorators/check/is_json.py +40 -0
  42. click_extended/decorators/check/is_mac_address.py +40 -0
  43. click_extended/decorators/check/is_negative.py +37 -0
  44. click_extended/decorators/check/is_non_zero.py +37 -0
  45. click_extended/decorators/check/is_port.py +39 -0
  46. click_extended/decorators/check/is_positive.py +37 -0
  47. click_extended/decorators/check/is_url.py +75 -0
  48. click_extended/decorators/check/is_uuid.py +40 -0
  49. click_extended/decorators/check/length.py +68 -0
  50. click_extended/decorators/check/not_empty.py +49 -0
  51. click_extended/decorators/check/regex.py +47 -0
  52. click_extended/decorators/check/requires.py +190 -0
  53. click_extended/decorators/check/starts_with.py +87 -0
  54. click_extended/decorators/check/truthy.py +37 -0
  55. click_extended/decorators/compare/__init__.py +15 -0
  56. click_extended/decorators/compare/at_least.py +57 -0
  57. click_extended/decorators/compare/at_most.py +57 -0
  58. click_extended/decorators/compare/between.py +119 -0
  59. click_extended/decorators/compare/greater_than.py +183 -0
  60. click_extended/decorators/compare/less_than.py +183 -0
  61. click_extended/decorators/convert/__init__.py +31 -0
  62. click_extended/decorators/convert/convert_angle.py +94 -0
  63. click_extended/decorators/convert/convert_area.py +123 -0
  64. click_extended/decorators/convert/convert_bits.py +211 -0
  65. click_extended/decorators/convert/convert_distance.py +154 -0
  66. click_extended/decorators/convert/convert_energy.py +155 -0
  67. click_extended/decorators/convert/convert_power.py +128 -0
  68. click_extended/decorators/convert/convert_pressure.py +131 -0
  69. click_extended/decorators/convert/convert_speed.py +122 -0
  70. click_extended/decorators/convert/convert_temperature.py +89 -0
  71. click_extended/decorators/convert/convert_time.py +108 -0
  72. click_extended/decorators/convert/convert_volume.py +218 -0
  73. click_extended/decorators/convert/convert_weight.py +158 -0
  74. click_extended/decorators/load/__init__.py +13 -0
  75. click_extended/decorators/load/load_csv.py +117 -0
  76. click_extended/decorators/load/load_json.py +61 -0
  77. click_extended/decorators/load/load_toml.py +47 -0
  78. click_extended/decorators/load/load_yaml.py +72 -0
  79. click_extended/decorators/math/__init__.py +37 -0
  80. click_extended/decorators/math/absolute.py +35 -0
  81. click_extended/decorators/math/add.py +48 -0
  82. click_extended/decorators/math/ceil.py +36 -0
  83. click_extended/decorators/math/clamp.py +51 -0
  84. click_extended/decorators/math/divide.py +42 -0
  85. click_extended/decorators/math/floor.py +36 -0
  86. click_extended/decorators/math/maximum.py +39 -0
  87. click_extended/decorators/math/minimum.py +39 -0
  88. click_extended/decorators/math/modulo.py +39 -0
  89. click_extended/decorators/math/multiply.py +51 -0
  90. click_extended/decorators/math/normalize.py +76 -0
  91. click_extended/decorators/math/power.py +39 -0
  92. click_extended/decorators/math/rounded.py +39 -0
  93. click_extended/decorators/math/sqrt.py +39 -0
  94. click_extended/decorators/math/subtract.py +39 -0
  95. click_extended/decorators/math/to_percent.py +63 -0
  96. click_extended/decorators/misc/__init__.py +17 -0
  97. click_extended/decorators/misc/choice.py +139 -0
  98. click_extended/decorators/misc/confirm_if.py +147 -0
  99. click_extended/decorators/misc/default.py +95 -0
  100. click_extended/decorators/misc/deprecated.py +131 -0
  101. click_extended/decorators/misc/experimental.py +79 -0
  102. click_extended/decorators/misc/now.py +42 -0
  103. click_extended/decorators/random/__init__.py +21 -0
  104. click_extended/decorators/random/random_bool.py +49 -0
  105. click_extended/decorators/random/random_choice.py +63 -0
  106. click_extended/decorators/random/random_datetime.py +140 -0
  107. click_extended/decorators/random/random_float.py +62 -0
  108. click_extended/decorators/random/random_integer.py +56 -0
  109. click_extended/decorators/random/random_prime.py +196 -0
  110. click_extended/decorators/random/random_string.py +77 -0
  111. click_extended/decorators/random/random_uuid.py +119 -0
  112. click_extended/decorators/transform/__init__.py +71 -0
  113. click_extended/decorators/transform/add_prefix.py +58 -0
  114. click_extended/decorators/transform/add_suffix.py +58 -0
  115. click_extended/decorators/transform/apply.py +35 -0
  116. click_extended/decorators/transform/basename.py +44 -0
  117. click_extended/decorators/transform/dirname.py +44 -0
  118. click_extended/decorators/transform/expand_vars.py +36 -0
  119. click_extended/decorators/transform/remove_prefix.py +57 -0
  120. click_extended/decorators/transform/remove_suffix.py +57 -0
  121. click_extended/decorators/transform/replace.py +46 -0
  122. click_extended/decorators/transform/slugify.py +45 -0
  123. click_extended/decorators/transform/split.py +43 -0
  124. click_extended/decorators/transform/strip.py +148 -0
  125. click_extended/decorators/transform/to_case.py +216 -0
  126. click_extended/decorators/transform/to_date.py +75 -0
  127. click_extended/decorators/transform/to_datetime.py +83 -0
  128. click_extended/decorators/transform/to_path.py +274 -0
  129. click_extended/decorators/transform/to_time.py +77 -0
  130. click_extended/decorators/transform/to_timestamp.py +114 -0
  131. click_extended/decorators/transform/truncate.py +47 -0
  132. click_extended/types.py +1 -1
  133. click_extended/utils/__init__.py +13 -0
  134. click_extended/utils/casing.py +169 -0
  135. click_extended/utils/checks.py +48 -0
  136. click_extended/utils/dispatch.py +1016 -0
  137. click_extended/utils/format.py +101 -0
  138. click_extended/utils/humanize.py +209 -0
  139. click_extended/utils/naming.py +238 -0
  140. click_extended/utils/process.py +294 -0
  141. click_extended/utils/selection.py +267 -0
  142. click_extended/utils/time.py +46 -0
  143. {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/METADATA +100 -29
  144. click_extended-1.0.1.dist-info/RECORD +149 -0
  145. click_extended-0.4.0.dist-info/RECORD +0 -10
  146. {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/WHEEL +0 -0
  147. {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/licenses/AUTHORS.md +0 -0
  148. {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/licenses/LICENSE +0 -0
  149. {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)