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,119 @@
1
+ """Parent node for generating a random UUID."""
2
+
3
+ import random
4
+ from typing import Any, Literal
5
+ from uuid import UUID, uuid1, uuid3, uuid5
6
+
7
+ from click_extended.classes import ParentNode
8
+ from click_extended.core.other.context import Context
9
+ from click_extended.types import Decorator
10
+
11
+
12
+ class RandomUUID(ParentNode):
13
+ """Parent node for generating a random UUID."""
14
+
15
+ @staticmethod
16
+ def _validate_namespace(namespace: UUID | str | None, version: int) -> UUID:
17
+ """Validate and convert namespace to UUID object."""
18
+ if namespace is None:
19
+ raise ValueError(
20
+ f"namespace is required for UUID version {version}"
21
+ )
22
+ if isinstance(namespace, str):
23
+ try:
24
+ return UUID(namespace)
25
+ except ValueError as exc:
26
+ raise ValueError(
27
+ f"Invalid namespace UUID format: '{namespace}'. "
28
+ f"Expected a valid UUID string "
29
+ f"(e.g., '6ba7b810-9dad-11d1-80b4-00c04fd430c8') "
30
+ f"or use predefined constants like uuid.NAMESPACE_DNS"
31
+ ) from exc
32
+ return namespace
33
+
34
+ def load(self, context: Context, *args: Any, **kwargs: Any) -> UUID:
35
+ if kwargs.get("seed") is not None:
36
+ random.seed(kwargs["seed"])
37
+
38
+ version = kwargs.get("version", 4)
39
+
40
+ if version == 1:
41
+ return uuid1()
42
+
43
+ if version == 3:
44
+ namespace = self._validate_namespace(kwargs.get("namespace"), 3)
45
+ uuid_name = kwargs.get("uuid_name")
46
+ if uuid_name is None:
47
+ raise ValueError("uuid_name is required for UUID version 3")
48
+ return uuid3(namespace, uuid_name)
49
+
50
+ if version == 4:
51
+ random_bytes = bytearray(random.getrandbits(8) for _ in range(16))
52
+ random_bytes[6] = (random_bytes[6] & 0x0F) | 0x40
53
+ random_bytes[8] = (random_bytes[8] & 0x3F) | 0x80
54
+ return UUID(bytes=bytes(random_bytes))
55
+
56
+ if version == 5:
57
+ namespace = self._validate_namespace(kwargs.get("namespace"), 5)
58
+ uuid_name = kwargs.get("uuid_name")
59
+ if uuid_name is None:
60
+ raise ValueError("uuid_name is required for UUID version 5")
61
+ return uuid5(namespace, uuid_name)
62
+
63
+ raise ValueError(
64
+ f"Unsupported UUID version: {version}. "
65
+ f"Supported versions are 1, 3, 4, and 5."
66
+ )
67
+
68
+
69
+ def random_uuid(
70
+ name: str,
71
+ version: Literal[1, 3, 4, 5] = 4,
72
+ namespace: UUID | str | None = None,
73
+ uuid_name: str | None = None,
74
+ seed: int | None = None,
75
+ ) -> Decorator:
76
+ """
77
+ Generate a random UUID.
78
+
79
+ Type: `ParentNode`
80
+
81
+ Args:
82
+ name (str):
83
+ The name of the parent node.
84
+ version (Literal[1, 3, 4, 5], optional):
85
+ The version of the UUID. Defaults to 4.
86
+ - Version 1: Time-based UUID (includes MAC address and timestamp)
87
+ - Version 3: MD5 hash of namespace + name (deterministic)
88
+ - Version 4: Random UUID (recommended for most use cases)
89
+ - Version 5: SHA-1 hash of namespace + name (deterministic)
90
+ namespace (UUID | str | None, optional):
91
+ The namespace UUID for versions 3 and 5. Required for those
92
+ versions. Must be a valid UUID object, a valid UUID string in the
93
+ format 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', or use predefined
94
+ constants from the uuid module (NAMESPACE_DNS, NAMESPACE_URL,
95
+ NAMESPACE_OID, NAMESPACE_X500). The namespace acts as a domain
96
+ identifier to generate deterministic UUIDs.
97
+ uuid_name (str | None, optional):
98
+ The name string for versions 3 and 5. Required for those versions.
99
+ This is combined with the namespace to generate a deterministic
100
+ UUID. The same namespace and name will always produce the same UUID.
101
+ seed (int | None, optional):
102
+ Optional seed for reproducible UUIDs (only affects version 4).
103
+
104
+ Returns:
105
+ Decorator:
106
+ The decorator function.
107
+
108
+ Raises:
109
+ ValueError:
110
+ If an unsupported UUID version is specified or if namespace/name
111
+ are missing for versions 3 or 5.
112
+ """
113
+ return RandomUUID.as_decorator(
114
+ name=name,
115
+ version=version,
116
+ namespace=namespace,
117
+ uuid_name=uuid_name,
118
+ seed=seed,
119
+ )
@@ -0,0 +1,71 @@
1
+ """Initialization file for the `click_extended.decorators.transform` module."""
2
+
3
+ from click_extended.decorators.transform.add_prefix import add_prefix
4
+ from click_extended.decorators.transform.add_suffix import add_suffix
5
+ from click_extended.decorators.transform.apply import apply
6
+ from click_extended.decorators.transform.basename import basename
7
+ from click_extended.decorators.transform.dirname import dirname
8
+ from click_extended.decorators.transform.expand_vars import expand_vars
9
+ from click_extended.decorators.transform.remove_prefix import remove_prefix
10
+ from click_extended.decorators.transform.remove_suffix import remove_suffix
11
+ from click_extended.decorators.transform.replace import replace
12
+ from click_extended.decorators.transform.slugify import slugify
13
+ from click_extended.decorators.transform.split import split
14
+ from click_extended.decorators.transform.strip import lstrip, rstrip, strip
15
+ from click_extended.decorators.transform.to_case import (
16
+ to_camel_case,
17
+ to_dot_case,
18
+ to_flat_case,
19
+ to_kebab_case,
20
+ to_lower_case,
21
+ to_meme_case,
22
+ to_pascal_case,
23
+ to_path_case,
24
+ to_screaming_snake_case,
25
+ to_snake_case,
26
+ to_title_case,
27
+ to_train_case,
28
+ to_upper_case,
29
+ )
30
+ from click_extended.decorators.transform.to_date import to_date
31
+ from click_extended.decorators.transform.to_datetime import to_datetime
32
+ from click_extended.decorators.transform.to_path import to_path
33
+ from click_extended.decorators.transform.to_time import to_time
34
+ from click_extended.decorators.transform.to_timestamp import to_timestamp
35
+ from click_extended.decorators.transform.truncate import truncate
36
+
37
+ __all__ = [
38
+ "add_prefix",
39
+ "add_suffix",
40
+ "apply",
41
+ "basename",
42
+ "dirname",
43
+ "expand_vars",
44
+ "lstrip",
45
+ "remove_prefix",
46
+ "remove_suffix",
47
+ "replace",
48
+ "rstrip",
49
+ "slugify",
50
+ "split",
51
+ "strip",
52
+ "to_camel_case",
53
+ "to_dot_case",
54
+ "to_flat_case",
55
+ "to_kebab_case",
56
+ "to_lower_case",
57
+ "to_meme_case",
58
+ "to_pascal_case",
59
+ "to_path_case",
60
+ "to_screaming_snake_case",
61
+ "to_snake_case",
62
+ "to_title_case",
63
+ "to_train_case",
64
+ "to_upper_case",
65
+ "to_date",
66
+ "to_datetime",
67
+ "to_path",
68
+ "to_time",
69
+ "to_timestamp",
70
+ "truncate",
71
+ ]
@@ -0,0 +1,58 @@
1
+ """Child node to add a prefix to a string."""
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 AddPrefix(ChildNode):
11
+ """Child node to add a prefix to a string."""
12
+
13
+ def handle_str(
14
+ self, value: str, context: Context, *args: Any, **kwargs: Any
15
+ ) -> Any:
16
+ prefix = kwargs["prefix"]
17
+ skip = kwargs["skip"]
18
+ case_sensitive = kwargs["case_sensitive"]
19
+
20
+ if skip:
21
+ if case_sensitive:
22
+ if value.startswith(prefix):
23
+ return value
24
+ else:
25
+ if value.lower().startswith(prefix.lower()):
26
+ return value
27
+
28
+ return prefix + value
29
+
30
+
31
+ def add_prefix(
32
+ prefix: str,
33
+ skip: bool = True,
34
+ case_sensitive: bool = False,
35
+ ) -> Decorator:
36
+ """
37
+ Add a prefix to a string.
38
+
39
+ Type: `ChildNode`
40
+
41
+ Supports: `str`
42
+
43
+ Args:
44
+ prefix (str):
45
+ The prefix to add.
46
+ skip (bool, optional):
47
+ Skip adding the prefix if it already exists. Defaults to `True`.
48
+ case_sensitive (bool)
49
+ Check for exact case matching when `skip=True`. Defaults to `False`.
50
+ Returns:
51
+ Decorator:
52
+ The decorated function.
53
+ """
54
+ return AddPrefix.as_decorator(
55
+ prefix=prefix,
56
+ skip=skip,
57
+ case_sensitive=case_sensitive,
58
+ )
@@ -0,0 +1,58 @@
1
+ """Child node to add a suffix to a string."""
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 AddSuffix(ChildNode):
11
+ """Child node to add a suffix to a string."""
12
+
13
+ def handle_str(
14
+ self, value: str, context: Context, *args: Any, **kwargs: Any
15
+ ) -> Any:
16
+ suffix = kwargs["suffix"]
17
+ skip = kwargs["skip"]
18
+ case_sensitive = kwargs["case_sensitive"]
19
+
20
+ if skip:
21
+ if case_sensitive:
22
+ if value.endswith(suffix):
23
+ return value
24
+ else:
25
+ if value.lower().endswith(suffix.lower()):
26
+ return value
27
+
28
+ return value + suffix
29
+
30
+
31
+ def add_suffix(
32
+ suffix: str,
33
+ skip: bool = True,
34
+ case_sensitive: bool = False,
35
+ ) -> Decorator:
36
+ """
37
+ Add a suffix to a string.
38
+
39
+ Type: `ChildNode`
40
+
41
+ Supports: `str`
42
+
43
+ Args:
44
+ suffix (str):
45
+ The suffix to add.
46
+ skip (bool, optional):
47
+ Skip adding the suffix if it already exists. Defaults to `True`.
48
+ case_sensitive (bool)
49
+ Check for exact case matching when `skip=True`. Defaults to `False`.
50
+ Returns:
51
+ Decorator:
52
+ The decorated function.
53
+ """
54
+ return AddSuffix.as_decorator(
55
+ suffix=suffix,
56
+ skip=skip,
57
+ case_sensitive=case_sensitive,
58
+ )
@@ -0,0 +1,35 @@
1
+ """Child decorator to apply an arbitrary function to any input."""
2
+
3
+ from typing import Any, Callable
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 Apply(ChildNode):
11
+ """Child decorator to apply an arbitrary function to any input."""
12
+
13
+ def handle_all(
14
+ self, value: Any, context: Context, *args: Any, **kwargs: Any
15
+ ) -> Any:
16
+ return kwargs["fn"](value)
17
+
18
+
19
+ def apply(fn: Callable[[Any], Any]) -> Decorator:
20
+ """
21
+ A decorator to apply an arbitrary function to all input.
22
+
23
+ Type: `ChildNode`
24
+
25
+ Supports: `Any`
26
+
27
+ Args:
28
+ fn (Callable[[Any], Any]):
29
+ The function to apply.
30
+
31
+ Returns:
32
+ Decorator:
33
+ The decorated function.
34
+ """
35
+ return Apply.as_decorator(fn=fn)
@@ -0,0 +1,44 @@
1
+ """Extract the base name from a path."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import click
8
+
9
+ from click_extended.core.nodes.child_node import ChildNode
10
+ from click_extended.core.other.context import Context
11
+ from click_extended.types import Decorator
12
+
13
+
14
+ class Basename(ChildNode):
15
+ """Extract the base name from a path."""
16
+
17
+ def handle_str(
18
+ self, value: str, context: Context, *args: Any, **kwargs: Any
19
+ ) -> str:
20
+ try:
21
+ return os.path.basename(value)
22
+ except ValueError as e:
23
+ raise click.BadParameter(str(e)) from e
24
+
25
+ def handle_path(
26
+ self, value: Path, context: Context, *args: Any, **kwargs: Any
27
+ ) -> str:
28
+ return value.name
29
+
30
+
31
+ def basename() -> Decorator:
32
+ """
33
+ Extract the base name from a path.
34
+
35
+ Example:
36
+ >>> @basename()
37
+ ... @option("--path")
38
+ ... def cli(path):
39
+ ... print(path)
40
+ ...
41
+ >>> cli(["--path", "/foo/bar/baz.txt"])
42
+ baz.txt
43
+ """
44
+ return Basename.as_decorator()
@@ -0,0 +1,44 @@
1
+ """Extract the directory name from a path."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import click
8
+
9
+ from click_extended.core.nodes.child_node import ChildNode
10
+ from click_extended.core.other.context import Context
11
+ from click_extended.types import Decorator
12
+
13
+
14
+ class Dirname(ChildNode):
15
+ """Extract the directory name from a path."""
16
+
17
+ def handle_str(
18
+ self, value: str, context: Context, *args: Any, **kwargs: Any
19
+ ) -> str:
20
+ try:
21
+ return os.path.dirname(value)
22
+ except ValueError as e:
23
+ raise click.BadParameter(str(e)) from e
24
+
25
+ def handle_path(
26
+ self, value: Path, context: Context, *args: Any, **kwargs: Any
27
+ ) -> Path:
28
+ return value.parent
29
+
30
+
31
+ def dirname() -> Decorator:
32
+ """
33
+ Extract the directory name from a path.
34
+
35
+ Example:
36
+ >>> @dirname()
37
+ ... @option("--path")
38
+ ... def cli(path):
39
+ ... print(path)
40
+ ...
41
+ >>> cli(["--path", "/foo/bar/baz.txt"])
42
+ /foo/bar
43
+ """
44
+ return Dirname.as_decorator()
@@ -0,0 +1,36 @@
1
+ """Expand environment variables in the string."""
2
+
3
+ import os
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 ExpandVars(ChildNode):
12
+ """Expand environment variables in the string."""
13
+
14
+ def handle_str(
15
+ self,
16
+ value: str,
17
+ context: Context,
18
+ *args: Any,
19
+ **kwargs: Any,
20
+ ) -> str:
21
+ return os.path.expandvars(value)
22
+
23
+
24
+ def expand_vars() -> Decorator:
25
+ """
26
+ Expand environment variables in the string.
27
+
28
+ Type: `ChildNode`
29
+
30
+ Supports: `str`
31
+
32
+ Returns:
33
+ Decorator:
34
+ The decorated function.
35
+ """
36
+ return ExpandVars.as_decorator()
@@ -0,0 +1,57 @@
1
+ """Child decorator to remove a prefix from a string."""
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 RemovePrefix(ChildNode):
11
+ """Child decorator to remove a prefix from a string."""
12
+
13
+ def handle_str(
14
+ self, value: str, context: Context, *args: Any, **kwargs: Any
15
+ ) -> str:
16
+ prefix: str = kwargs["prefix"]
17
+
18
+ if value.startswith(prefix):
19
+ return value[len(prefix) :]
20
+
21
+ return value
22
+
23
+
24
+ def remove_prefix(prefix: str) -> Decorator:
25
+ """
26
+ Remove a prefix from a string if it exists.
27
+
28
+ Type: `ChildNode`
29
+
30
+ Supports: `str`
31
+
32
+ Args:
33
+ prefix (str):
34
+ The prefix to remove from the beginning of the string.
35
+
36
+ Returns:
37
+ Decorator:
38
+ The decorator function.
39
+
40
+ Examples:
41
+ ```python
42
+ @command()
43
+ @option("name", default="Mr. John")
44
+ @remove_prefix("Mr. ")
45
+ def cmd(name: str) -> None:
46
+ click.echo(f"Name: {name}") # Output: Name: John
47
+ ```
48
+
49
+ ```python
50
+ @command()
51
+ @option("url", default="https://example.com")
52
+ @remove_prefix("https://")
53
+ def cmd(url: str) -> None:
54
+ click.echo(f"Domain: {url}") # Output: Domain: example.com
55
+ ```
56
+ """
57
+ return RemovePrefix.as_decorator(prefix=prefix)
@@ -0,0 +1,57 @@
1
+ """Child decorator to remove a suffix from a string."""
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 RemoveSuffix(ChildNode):
11
+ """Child decorator to remove a suffix from a string."""
12
+
13
+ def handle_str(
14
+ self, value: str, context: Context, *args: Any, **kwargs: Any
15
+ ) -> str:
16
+ suffix: str = kwargs["suffix"]
17
+
18
+ if value.endswith(suffix):
19
+ return value[: -len(suffix)]
20
+
21
+ return value
22
+
23
+
24
+ def remove_suffix(suffix: str) -> Decorator:
25
+ """
26
+ Remove a suffix from a string if it exists.
27
+
28
+ Type: `ChildNode`
29
+
30
+ Supports: `str`
31
+
32
+ Args:
33
+ suffix (str):
34
+ The suffix to remove from the end of the string.
35
+
36
+ Returns:
37
+ Decorator:
38
+ The decorator function.
39
+
40
+ Examples:
41
+ ```python
42
+ @command()
43
+ @option("filename", default="document.txt")
44
+ @remove_suffix(".txt")
45
+ def cmd(filename: str) -> None:
46
+ click.echo(f"Name: {filename}") # Output: Name: document
47
+ ```
48
+
49
+ ```python
50
+ @command()
51
+ @option("url", default="example.com/")
52
+ @remove_suffix("/")
53
+ def cmd(url: str) -> None:
54
+ click.echo(f"URL: {url}") # Output: URL: example.com
55
+ ```
56
+ """
57
+ return RemoveSuffix.as_decorator(suffix=suffix)
@@ -0,0 +1,46 @@
1
+ """Replace occurrences of a substring with another."""
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 Replace(ChildNode):
11
+ """Replace occurrences of a substring with another."""
12
+
13
+ def handle_str(
14
+ self,
15
+ value: str,
16
+ context: Context,
17
+ *args: Any,
18
+ **kwargs: Any,
19
+ ) -> str:
20
+ old = kwargs["old"]
21
+ new = kwargs["new"]
22
+ count = kwargs.get("count", -1)
23
+ return value.replace(old, new, count)
24
+
25
+
26
+ def replace(old: str, new: str, count: int = -1) -> Decorator:
27
+ """
28
+ Replace occurrences of a substring with another.
29
+
30
+ Type: `ChildNode`
31
+
32
+ Supports: `str`
33
+
34
+ Args:
35
+ old (str):
36
+ The substring to replace.
37
+ new (str):
38
+ The replacement substring.
39
+ count (int):
40
+ Maximum number of occurrences to replace. Defaults to `-1` (all).
41
+
42
+ Returns:
43
+ Decorator:
44
+ The decorated function.
45
+ """
46
+ return Replace.as_decorator(old=old, new=new, count=count)
@@ -0,0 +1,45 @@
1
+ """Convert the string to a slug."""
2
+
3
+ from typing import Any
4
+
5
+ from slugify import slugify as _slugify
6
+
7
+ from click_extended.core.nodes.child_node import ChildNode
8
+ from click_extended.core.other.context import Context
9
+ from click_extended.types import Decorator
10
+
11
+
12
+ class Slugify(ChildNode):
13
+ """Convert the string to a slug."""
14
+
15
+ def handle_str(
16
+ self,
17
+ value: str,
18
+ context: Context,
19
+ *args: Any,
20
+ **kwargs: Any,
21
+ ) -> str:
22
+ return _slugify(value, **kwargs)
23
+
24
+
25
+ def slugify(**kwargs: Any) -> Decorator:
26
+ """
27
+ Convert the string to a slug.
28
+
29
+ This decorator uses the `python-slugify` library under the hood.
30
+
31
+ Read more about the library here: https://pypi.org/project/python-slugify/
32
+
33
+ Type: `ChildNode`
34
+
35
+ Supports: `str`
36
+
37
+ Args:
38
+ **kwargs (Any):
39
+ Additional keyword arguments passed to `slugify.slugify`.
40
+
41
+ Returns:
42
+ Decorator:
43
+ The decorated function.
44
+ """
45
+ return Slugify.as_decorator(**kwargs)