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,158 @@
1
+ """Convert between various weight units."""
2
+
3
+ from decimal import Decimal, getcontext
4
+ from typing import Any, Literal
5
+
6
+ from click_extended.core.nodes.child_node import ChildNode
7
+ from click_extended.core.other.context import Context
8
+ from click_extended.types import Decorator
9
+
10
+ getcontext().prec = 28
11
+
12
+ MASS_UNITS = {
13
+ "ug": Decimal("1e-9"),
14
+ "mg": Decimal("1e-6"),
15
+ "g": Decimal("1e-3"),
16
+ "kg": Decimal("1"),
17
+ "t": Decimal("1000"),
18
+ "lb": Decimal("0.45359237"),
19
+ "oz": Decimal("0.028349523125"),
20
+ "st": Decimal("6.35029318"),
21
+ "ct": Decimal("0.0002"),
22
+ "amu": Decimal("1.66053906660e-27"),
23
+ "slg": Decimal("14.593903"),
24
+ }
25
+
26
+ FORCE_UNITS = {
27
+ "n": Decimal("1"),
28
+ "kn": Decimal("1000"),
29
+ "lbf": Decimal("4.4482216152605"),
30
+ "ozf": Decimal("0.27801385095378125"),
31
+ "kgf": Decimal("9.80665"),
32
+ "dyn": Decimal("1e-5"),
33
+ }
34
+
35
+
36
+ class ConvertWeight(ChildNode):
37
+ """Convert between various weight units."""
38
+
39
+ def handle_numeric(
40
+ self,
41
+ value: int | float,
42
+ context: Context,
43
+ *args: Any,
44
+ **kwargs: Any,
45
+ ) -> float:
46
+ from_unit = kwargs["from_unit"]
47
+ to_unit = kwargs["to_unit"]
48
+ gravity = Decimal(str(kwargs["gravity"]))
49
+ val = Decimal(str(value))
50
+
51
+ if from_unit in MASS_UNITS:
52
+ base_value = val * MASS_UNITS[from_unit]
53
+ is_mass = True
54
+ elif from_unit in FORCE_UNITS:
55
+ base_value = val * FORCE_UNITS[from_unit]
56
+ is_mass = False
57
+ else:
58
+ raise ValueError(f"Unknown unit '{from_unit}'")
59
+
60
+ if is_mass and to_unit in FORCE_UNITS:
61
+ base_value = base_value * gravity
62
+ elif not is_mass and to_unit in MASS_UNITS:
63
+ base_value = base_value / gravity
64
+
65
+ if to_unit in MASS_UNITS:
66
+ result = base_value / MASS_UNITS[to_unit]
67
+ elif to_unit in FORCE_UNITS:
68
+ result = base_value / FORCE_UNITS[to_unit]
69
+ else:
70
+ raise ValueError(f"Unknown unit '{to_unit}'")
71
+
72
+ return float(result)
73
+
74
+
75
+ def convert_weight(
76
+ from_unit: Literal[
77
+ "ug",
78
+ "mg",
79
+ "g",
80
+ "kg",
81
+ "t",
82
+ "lb",
83
+ "oz",
84
+ "st",
85
+ "n",
86
+ "kn",
87
+ "ct",
88
+ "amu",
89
+ "lbf",
90
+ "ozf",
91
+ "kgf",
92
+ "dyn",
93
+ "slg",
94
+ ],
95
+ to_unit: Literal[
96
+ "ug",
97
+ "mg",
98
+ "g",
99
+ "kg",
100
+ "t",
101
+ "lb",
102
+ "oz",
103
+ "st",
104
+ "n",
105
+ "kn",
106
+ "ct",
107
+ "amu",
108
+ "lbf",
109
+ "ozf",
110
+ "kgf",
111
+ "dyn",
112
+ "slg",
113
+ ],
114
+ gravity: float = 9.80665,
115
+ ) -> Decorator:
116
+ """
117
+ Convert between various weight units.
118
+
119
+ Type: `ChildNode`
120
+
121
+ Supports: `int`, `float`
122
+
123
+ Units:
124
+ - **ug**: Micrograms (mass)
125
+ - **mg**: Milligrams (mass)
126
+ - **g**: Grams (mass)
127
+ - **kg**: Kilograms (mass)
128
+ - **t**: Metric tonne (mass)
129
+ - **lb**: Pound (mass)
130
+ - **oz**: Ounce (mass)
131
+ - **st**: Stone (mass)
132
+ - **n**: Newton (force)
133
+ - **kn**: Kilonewton (force)
134
+ - **ct**: Carat (mass)
135
+ - **amu**: Atomic mass unit (mass)
136
+ - **lbf**: Pound-force (force)
137
+ - **ozf**: Ounce-force (force)
138
+ - **kgf**: Kilogram-force (force)
139
+ - **dyn**: Dyne (force)
140
+ - **slg**: Slug (mass)
141
+
142
+ Args:
143
+ from_unit (str):
144
+ The unit to convert from.
145
+ to_unit (str):
146
+ The unit to convert to.
147
+ gravity (float, optional):
148
+ The gravity constant, used for force units. Defaults to `9.80665`.
149
+
150
+ Returns:
151
+ Decorator:
152
+ The decorated function.
153
+ """
154
+ return ConvertWeight.as_decorator(
155
+ from_unit=from_unit,
156
+ to_unit=to_unit,
157
+ gravity=gravity,
158
+ )
@@ -0,0 +1,13 @@
1
+ """Initialization file for the `click_extended.decorators.load` module."""
2
+
3
+ from click_extended.decorators.load.load_csv import load_csv
4
+ from click_extended.decorators.load.load_json import load_json
5
+ from click_extended.decorators.load.load_toml import load_toml
6
+ from click_extended.decorators.load.load_yaml import load_yaml
7
+
8
+ __all__ = [
9
+ "load_csv",
10
+ "load_json",
11
+ "load_toml",
12
+ "load_yaml",
13
+ ]
@@ -0,0 +1,117 @@
1
+ """Child decorator to load the contents of a CSV file."""
2
+
3
+ # pylint: disable=too-many-locals
4
+ # pylint: disable=too-many-arguments
5
+ # pylint: disable=too-many-positional-arguments
6
+
7
+ import csv
8
+ from pathlib import Path
9
+ from typing import Any, Literal
10
+
11
+ from click_extended.core.nodes.child_node import ChildNode
12
+ from click_extended.core.other.context import Context
13
+ from click_extended.types import Decorator
14
+
15
+
16
+ class LoadCsv(ChildNode):
17
+ """Child decorator to load the contents of a CSV file."""
18
+
19
+ def handle_path(
20
+ self, value: Path, context: Context, *args: Any, **kwargs: Any
21
+ ) -> list[dict[str, str]] | list[list[str]]:
22
+ dialect = kwargs["dialect"]
23
+ delimiter = kwargs["delimiter"]
24
+ has_header = kwargs["has_header"]
25
+ as_dict = kwargs["as_dict"]
26
+ encoding = kwargs["encoding"]
27
+ skip_empty = kwargs["skip_empty"]
28
+
29
+ if value.is_dir():
30
+ raise IsADirectoryError(
31
+ f"Path '{value.absolute()}' is a directory, but must be a file."
32
+ )
33
+
34
+ with value.open("r", encoding=encoding, newline="") as f:
35
+ reader_kwargs: dict[str, Any] = {}
36
+ if dialect:
37
+ reader_kwargs["dialect"] = dialect
38
+ if delimiter:
39
+ reader_kwargs["delimiter"] = delimiter
40
+
41
+ if as_dict:
42
+ reader = csv.DictReader(f, **reader_kwargs)
43
+ rows: list[dict[str, str]] = []
44
+ for row_dict in reader:
45
+ if skip_empty and not any(row_dict.values()):
46
+ continue
47
+ rows.append(row_dict)
48
+ return rows
49
+
50
+ reader_list = csv.reader(f, **reader_kwargs)
51
+ rows_list: list[list[str]] = []
52
+
53
+ if has_header:
54
+ next(reader_list, None) # Skip header row
55
+
56
+ for row_list in reader_list:
57
+ if skip_empty and not any(row_list):
58
+ continue
59
+ rows_list.append(row_list)
60
+ return rows_list
61
+
62
+
63
+ def load_csv(
64
+ dialect: Literal["excel", "excel-tab", "unix"] | None = None,
65
+ delimiter: str | None = None,
66
+ has_header: bool = True,
67
+ as_dict: bool = True,
68
+ encoding: str = "utf-8",
69
+ skip_empty: bool = True,
70
+ ) -> Decorator:
71
+ """
72
+ Load the contents of a CSV file.
73
+
74
+ Type: `ChildNode`
75
+
76
+ Supports: `pathlib.Path`
77
+
78
+ Args:
79
+ dialect (Literal["excel", "excel-tab", "unix"] | None, optional):
80
+ CSV dialect to use:
81
+ - `"excel"`: Excel-generated CSV files (comma-delimited)
82
+ - `"excel-tab"`: Excel-generated tab-delimited files
83
+ - `"unix"`: Unix-style CSV files (quote all fields)
84
+ If not specified, the reader will use default settings.
85
+ Defaults to `None`.
86
+ delimiter (str, optional):
87
+ Character used to separate fields. Common values are ',' and '\\t'.
88
+ If not specified, defaults to comma for most dialects.
89
+ Defaults to `None`.
90
+ has_header (bool, optional):
91
+ Whether the CSV file has a header row. Only used when
92
+ `as_dict=False`. When `as_dict=True`, the first row is
93
+ always treated as headers. Defaults to `True`.
94
+ as_dict (bool, optional):
95
+ Whether to return rows as dictionaries (using header as keys)
96
+ or as lists. When `True`, uses `csv.DictReader`. When `False`,
97
+ uses `csv.reader`.
98
+ Defaults to `True`.
99
+ encoding (str, optional):
100
+ The encoding to use when reading the file.
101
+ Defaults to `"utf-8"`.
102
+ skip_empty (bool, optional):
103
+ Whether to skip empty rows in the CSV file.
104
+ Defaults to `True`.
105
+
106
+ Returns:
107
+ Decorator:
108
+ The decorated function.
109
+ """
110
+ return LoadCsv.as_decorator(
111
+ dialect=dialect,
112
+ delimiter=delimiter,
113
+ has_header=has_header,
114
+ as_dict=as_dict,
115
+ encoding=encoding,
116
+ skip_empty=skip_empty,
117
+ )
@@ -0,0 +1,61 @@
1
+ """Child decorator to load contents from a JSON file."""
2
+
3
+ import json
4
+ from decimal import Decimal
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from click_extended.core.nodes.child_node import ChildNode
9
+ from click_extended.core.other.context import Context
10
+ from click_extended.types import Decorator
11
+
12
+
13
+ class LoadJson(ChildNode):
14
+ """Child decorator to load contents from a JSON file."""
15
+
16
+ def handle_path(
17
+ self, value: Path, context: Context, *args: Any, **kwargs: Any
18
+ ) -> Any:
19
+ encoding = kwargs["encoding"]
20
+ strict = kwargs["strict"]
21
+
22
+ if value.is_dir():
23
+ raise IsADirectoryError(
24
+ f"Path '{value.absolute()}' is a directory, but must be a file."
25
+ )
26
+
27
+ with value.open("r", encoding=encoding) as f:
28
+ if strict:
29
+ return json.load(f, parse_float=Decimal)
30
+ return json.load(f)
31
+
32
+
33
+ def load_json(
34
+ encoding: str = "utf-8",
35
+ strict: bool = True,
36
+ ) -> Decorator:
37
+ """
38
+ Load a JSON file from a `pathlib.Path` object.
39
+
40
+ Type: `ChildNode`
41
+
42
+ Supports: `pathlib.Path`
43
+
44
+ Args:
45
+ encoding (str, optional):
46
+ The encoding to use when reading the file.
47
+ Defaults to `"utf-8"`.
48
+ strict (bool, optional):
49
+ Whether to use strict parsing for numerical values.
50
+ When `True`, floats are parsed as `Decimal` for precision.
51
+ When `False`, floats are parsed as standard Python `float`.
52
+ Defaults to `True`.
53
+
54
+ Returns:
55
+ Decorator:
56
+ The decorated function.
57
+ """
58
+ return LoadJson.as_decorator(
59
+ encoding=encoding,
60
+ strict=strict,
61
+ )
@@ -0,0 +1,47 @@
1
+ """Child decorator to load the contents of a TOML file."""
2
+
3
+ # pylint: disable=wrong-import-position
4
+
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ if sys.version_info >= (3, 11):
10
+ import tomllib
11
+ else:
12
+ import tomli as tomllib # type: ignore[import-not-found]
13
+
14
+ from click_extended.core.nodes.child_node import ChildNode
15
+ from click_extended.core.other.context import Context
16
+ from click_extended.types import Decorator
17
+
18
+
19
+ class LoadToml(ChildNode):
20
+ """Child decorator to load the contents of a TOML file."""
21
+
22
+ def handle_path(
23
+ self, value: Path, context: Context, *args: Any, **kwargs: Any
24
+ ) -> dict[str, Any]:
25
+ if value.is_dir():
26
+ raise IsADirectoryError(
27
+ f"Path '{value.absolute()}' is a directory, but must be a file."
28
+ )
29
+
30
+ with value.open("rb") as f:
31
+ result: dict[str, Any] = tomllib.load(f)
32
+ return result
33
+
34
+
35
+ def load_toml() -> Decorator:
36
+ """
37
+ Load the contents of a TOML file from a `pathlib.Path` object.
38
+
39
+ Type: `ChildNode`
40
+
41
+ Supports: `pathlib.Path`
42
+
43
+ Returns:
44
+ Decorator:
45
+ The decorated function.
46
+ """
47
+ return LoadToml.as_decorator()
@@ -0,0 +1,72 @@
1
+ """Child decorator to load the contents of a YAML file."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Literal
5
+
6
+ from yaml import FullLoader, SafeLoader, UnsafeLoader, load
7
+
8
+ from click_extended.core.nodes.child_node import ChildNode
9
+ from click_extended.core.other.context import Context
10
+ from click_extended.types import Decorator
11
+
12
+
13
+ class LoadYaml(ChildNode):
14
+ """Child decorator to load the contents of a YAML file."""
15
+
16
+ def handle_path(
17
+ self, value: Path, context: Context, *args: Any, **kwargs: Any
18
+ ) -> Any:
19
+ if value.is_dir():
20
+ raise IsADirectoryError(
21
+ f"Path '{value.absolute()}' is a directory, but must be a file."
22
+ )
23
+
24
+ loader_name = kwargs["loader"]
25
+ loader: type[SafeLoader] | type[FullLoader] | type[UnsafeLoader]
26
+
27
+ if loader_name == "safe":
28
+ loader = SafeLoader
29
+ elif loader_name == "unsafe":
30
+ loader = UnsafeLoader
31
+ else:
32
+ loader = FullLoader
33
+
34
+ with value.open("r", encoding=kwargs["encoding"]) as f:
35
+ return load(f, Loader=loader)
36
+
37
+
38
+ def load_yaml(
39
+ encoding: str = "utf-8",
40
+ loader: Literal["safe", "unsafe", "full"] = "safe",
41
+ ) -> Decorator:
42
+ """
43
+ Load the contents of a YAML file.
44
+
45
+ Type: `ChildNode`
46
+
47
+ Supports: `pathlib.Path`
48
+
49
+ Args:
50
+ encoding (str, optional):
51
+ The encoding to use when reading the file.
52
+ Defaults to `"utf-8"`.
53
+ loader (Literal["safe", "unsafe", "full"], optional):
54
+ The YAML loader to use:
55
+ - `"safe"`: SafeLoader - Only constructs simple Python objects
56
+ (strings, lists, dicts, numbers, dates). Recommended for
57
+ untrusted input.
58
+ - `"unsafe"`: UnsafeLoader - Can construct arbitrary Python
59
+ objects. Use only with trusted YAML files.
60
+ - `"full"`: FullLoader - Constructs simple Python objects and
61
+ some additional types. Safer than unsafe but less restrictive
62
+ than safe.
63
+ Defaults to `"safe"`.
64
+
65
+ Returns:
66
+ Decorator:
67
+ The decorated function.
68
+ """
69
+ return LoadYaml.as_decorator(
70
+ encoding=encoding,
71
+ loader=loader,
72
+ )
@@ -0,0 +1,37 @@
1
+ """Initialization file for the `click_extended.decorators.math` module."""
2
+
3
+ from click_extended.decorators.math.absolute import absolute
4
+ from click_extended.decorators.math.add import add
5
+ from click_extended.decorators.math.ceil import ceil
6
+ from click_extended.decorators.math.clamp import clamp
7
+ from click_extended.decorators.math.divide import divide
8
+ from click_extended.decorators.math.floor import floor
9
+ from click_extended.decorators.math.maximum import maximum
10
+ from click_extended.decorators.math.minimum import minimum
11
+ from click_extended.decorators.math.modulo import modulo
12
+ from click_extended.decorators.math.multiply import multiply
13
+ from click_extended.decorators.math.normalize import normalize
14
+ from click_extended.decorators.math.power import power
15
+ from click_extended.decorators.math.rounded import rounded
16
+ from click_extended.decorators.math.sqrt import sqrt
17
+ from click_extended.decorators.math.subtract import subtract
18
+ from click_extended.decorators.math.to_percent import to_percent
19
+
20
+ __all__ = [
21
+ "absolute",
22
+ "add",
23
+ "ceil",
24
+ "clamp",
25
+ "divide",
26
+ "floor",
27
+ "maximum",
28
+ "minimum",
29
+ "modulo",
30
+ "multiply",
31
+ "normalize",
32
+ "power",
33
+ "rounded",
34
+ "sqrt",
35
+ "subtract",
36
+ "to_percent",
37
+ ]
@@ -0,0 +1,35 @@
1
+ """Return the absolute value of the input."""
2
+
3
+ from typing import Any
4
+
5
+ from click_extended.core.nodes.child_node import ChildNode
6
+ from click_extended.core.other.context import Context
7
+ from click_extended.types import Decorator
8
+
9
+
10
+ class Absolute(ChildNode):
11
+ """Return the absolute value of the input."""
12
+
13
+ def handle_numeric(
14
+ self,
15
+ value: int | float,
16
+ context: Context,
17
+ *args: Any,
18
+ **kwargs: Any,
19
+ ) -> Any:
20
+ return abs(value)
21
+
22
+
23
+ def absolute() -> Decorator:
24
+ """
25
+ Return the absolute value of the input.
26
+
27
+ Type: `ChildNode`
28
+
29
+ Supports: `int`, `float`
30
+
31
+ Returns:
32
+ Decorator:
33
+ The decorated function.
34
+ """
35
+ return Absolute.as_decorator()
@@ -0,0 +1,48 @@
1
+ """Add a value to the input."""
2
+
3
+ from typing import Any
4
+
5
+ from click_extended.core.nodes.child_node import ChildNode
6
+ from click_extended.core.other.context import Context
7
+ from click_extended.types import Decorator
8
+
9
+
10
+ class Add(ChildNode):
11
+ """Add a value to the input."""
12
+
13
+ def handle_numeric(
14
+ self,
15
+ value: int | float,
16
+ context: Context,
17
+ *args: Any,
18
+ **kwargs: Any,
19
+ ) -> Any:
20
+ return value + kwargs["n"]
21
+
22
+ def handle_str(
23
+ self,
24
+ value: str,
25
+ context: Context,
26
+ *args: Any,
27
+ **kwargs: Any,
28
+ ) -> Any:
29
+ return value + str(kwargs["n"])
30
+
31
+
32
+ def add(n: int | float | str) -> Decorator:
33
+ """
34
+ Add a value to the input.
35
+
36
+ Type: `ChildNode`
37
+
38
+ Supports: `int`, `float`, `str`
39
+
40
+ Args:
41
+ n (int | float | str):
42
+ The value to add.
43
+
44
+ Returns:
45
+ Decorator:
46
+ The decorated function.
47
+ """
48
+ return Add.as_decorator(n=n)
@@ -0,0 +1,36 @@
1
+ """Calculate the ceiling of the input."""
2
+
3
+ import math
4
+ from typing import Any
5
+
6
+ from click_extended.core.nodes.child_node import ChildNode
7
+ from click_extended.core.other.context import Context
8
+ from click_extended.types import Decorator
9
+
10
+
11
+ class Ceil(ChildNode):
12
+ """Calculate the ceiling of the input."""
13
+
14
+ def handle_numeric(
15
+ self,
16
+ value: int | float,
17
+ context: Context,
18
+ *args: Any,
19
+ **kwargs: Any,
20
+ ) -> Any:
21
+ return math.ceil(value)
22
+
23
+
24
+ def ceil() -> Decorator:
25
+ """
26
+ Calculate the ceiling of the input.
27
+
28
+ Type: `ChildNode`
29
+
30
+ Supports: `int`, `float`
31
+
32
+ Returns:
33
+ Decorator:
34
+ The decorated function.
35
+ """
36
+ return Ceil.as_decorator()
@@ -0,0 +1,51 @@
1
+ """Clamp the input value between a minimum and maximum."""
2
+
3
+ from typing import Any
4
+
5
+ from click_extended.core.nodes.child_node import ChildNode
6
+ from click_extended.core.other.context import Context
7
+ from click_extended.types import Decorator
8
+
9
+
10
+ class Clamp(ChildNode):
11
+ """Clamp the input value between a minimum and maximum."""
12
+
13
+ def handle_numeric(
14
+ self,
15
+ value: int | float,
16
+ context: Context,
17
+ *args: Any,
18
+ **kwargs: Any,
19
+ ) -> Any:
20
+ min_val = kwargs.get("min_val")
21
+ max_val = kwargs.get("max_val")
22
+
23
+ if min_val is not None and value < min_val:
24
+ return min_val
25
+ if max_val is not None and value > max_val:
26
+ return max_val
27
+ return value
28
+
29
+
30
+ def clamp(
31
+ min_val: int | float | None = None,
32
+ max_val: int | float | None = None,
33
+ ) -> Decorator:
34
+ """
35
+ Clamp the input value between a minimum and maximum.
36
+
37
+ Type: `ChildNode`
38
+
39
+ Supports: `int`, `float`
40
+
41
+ Args:
42
+ min_val (int | float, optional):
43
+ The minimum value.
44
+ max_val (int | float, optional):
45
+ The maximum value.
46
+
47
+ Returns:
48
+ Decorator:
49
+ The decorated function.
50
+ """
51
+ return Clamp.as_decorator(min_val=min_val, max_val=max_val)