dycw-actions 0.3.40__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 dycw-actions might be problematic. Click here for more details.

actions/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from __future__ import annotations
2
+
3
+ __version__ = "0.3.40"
actions/cli.py ADDED
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from click import group
4
+ from utilities.click import CONTEXT_SETTINGS
5
+
6
+ from actions.hooks.cli import hooks_sub_cmd
7
+ from actions.publish.cli import publish_sub_cmd
8
+ from actions.requirements.cli import requirements_sub_cmd
9
+ from actions.sequence_strs.cli import sequence_strs_sub_cmd
10
+ from actions.sleep.cli import sleep_sub_cmd
11
+ from actions.tag.cli import tag_sub_cmd
12
+
13
+
14
+ @group(**CONTEXT_SETTINGS)
15
+ def _main() -> None: ...
16
+
17
+
18
+ _ = _main.command(name="hooks", **CONTEXT_SETTINGS)(hooks_sub_cmd)
19
+ _ = _main.command(name="publish", **CONTEXT_SETTINGS)(publish_sub_cmd)
20
+ _ = _main.command(name="requirements", **CONTEXT_SETTINGS)(requirements_sub_cmd)
21
+ _ = _main.command(name="sequence-strs", **CONTEXT_SETTINGS)(sequence_strs_sub_cmd)
22
+ _ = _main.command(name="sleep", **CONTEXT_SETTINGS)(sleep_sub_cmd)
23
+ _ = _main.command(name="tag", **CONTEXT_SETTINGS)(tag_sub_cmd)
24
+
25
+
26
+ if __name__ == "__main__":
27
+ _main()
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ GITHUB_TOKEN = "${{github.token}}" # noqa: S105
4
+ PRERELEASE = "disallow"
5
+ RESOLUTION = "highest"
6
+
7
+
8
+ __all__ = ["GITHUB_TOKEN", "PRERELEASE", "RESOLUTION"]
@@ -0,0 +1,189 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from actions.conformalize.defaults import GITHUB_TOKEN, PRERELEASE, RESOLUTION
6
+
7
+ if TYPE_CHECKING:
8
+ from actions.types import StrDict
9
+
10
+
11
+ def run_action_pre_commit_dict(
12
+ *,
13
+ token_checkout: str = GITHUB_TOKEN,
14
+ submodules: str | None = None,
15
+ token_uv: str = GITHUB_TOKEN,
16
+ repos: Any | None = None,
17
+ hooks: Any | None = None,
18
+ sleep: int = 1,
19
+ ) -> StrDict:
20
+ dict_: StrDict = {"token-checkout": token_checkout}
21
+ _add_item(dict_, "submodules", value=submodules)
22
+ dict_["token-uv"] = token_uv
23
+ _add_item(dict_, "repos", value=repos)
24
+ _add_item(dict_, "hooks", value=hooks)
25
+ dict_["sleep"] = sleep
26
+ return {
27
+ "name": "Run 'pre-commit'",
28
+ "uses": "dycw/action-pre-commit@latest",
29
+ "with": dict_,
30
+ }
31
+
32
+
33
+ def run_action_publish_dict(
34
+ *,
35
+ token_checkout: str = GITHUB_TOKEN,
36
+ token_uv: str = GITHUB_TOKEN,
37
+ username: str | None = None,
38
+ password: str | None = None,
39
+ publish_url: str | None = None,
40
+ trusted_publishing: bool = False,
41
+ native_tls: bool = False,
42
+ ) -> StrDict:
43
+ dict_: StrDict = {"token-checkout": token_checkout, "token-uv": token_uv}
44
+ _add_item(dict_, "username", value=username)
45
+ _add_item(dict_, "password", value=password)
46
+ _add_item(dict_, "publish-url", value=publish_url)
47
+ _add_boolean(dict_, "trusted-publishing", value=trusted_publishing)
48
+ _add_native_tls(dict_, native_tls=native_tls)
49
+ return {
50
+ "name": "Build and publish package",
51
+ "uses": "dycw/action-publish@latest",
52
+ "with": dict_,
53
+ }
54
+
55
+
56
+ def run_action_pyright_dict(
57
+ *,
58
+ token_checkout: str = GITHUB_TOKEN,
59
+ token_uv: str = GITHUB_TOKEN,
60
+ python_version: str | None = None,
61
+ resolution: str = RESOLUTION,
62
+ prerelease: str = PRERELEASE,
63
+ native_tls: bool = False,
64
+ with_requirements: str | None = None,
65
+ ) -> StrDict:
66
+ dict_: StrDict = {"token-checkout": token_checkout, "token-uv": token_uv}
67
+ _add_python_version(dict_, python_version=python_version)
68
+ dict_["resolution"] = resolution
69
+ dict_["prerelease"] = prerelease
70
+ _add_native_tls(dict_, native_tls=native_tls)
71
+ _add_with_requirements(dict_, with_requirements=with_requirements)
72
+ return {
73
+ "name": "Run 'pyright'",
74
+ "uses": "dycw/action-pyright@latest",
75
+ "with": dict_,
76
+ }
77
+
78
+
79
+ def run_action_pytest_dict(
80
+ *,
81
+ token_checkout: str = GITHUB_TOKEN,
82
+ token_uv: str = GITHUB_TOKEN,
83
+ python_version: str | None = None,
84
+ resolution: str = RESOLUTION,
85
+ prerelease: str = PRERELEASE,
86
+ native_tls: bool = False,
87
+ with_requirements: str | None = None,
88
+ ) -> StrDict:
89
+ dict_: StrDict = {"token-checkout": token_checkout, "token-uv": token_uv}
90
+ _add_python_version(dict_, python_version=python_version)
91
+ dict_["resolution"] = resolution
92
+ dict_["prerelease"] = prerelease
93
+ _add_native_tls(dict_, native_tls=native_tls)
94
+ _add_with_requirements(dict_, with_requirements=with_requirements)
95
+ return {"name": "Run 'pytest'", "uses": "dycw/action-pytest@latest", "with": dict_}
96
+
97
+
98
+ def run_action_random_sleep_dict(
99
+ *,
100
+ token_checkout: str = GITHUB_TOKEN,
101
+ token_uv: str = GITHUB_TOKEN,
102
+ min: int = 0, # noqa: A002
103
+ max: int = 3600, # noqa: A002
104
+ step: int = 1,
105
+ log_freq: int = 1,
106
+ ) -> StrDict:
107
+ dict_: StrDict = {
108
+ "token-checkout": token_checkout,
109
+ "token-uv": token_uv,
110
+ "min": min,
111
+ "max": max,
112
+ "step": step,
113
+ "log-freq": log_freq,
114
+ }
115
+ return {
116
+ "name": "Tag latest commit",
117
+ "uses": "dycw/action-tag@latest",
118
+ "with": dict_,
119
+ }
120
+
121
+
122
+ def run_action_ruff_dict(
123
+ *, token_checkout: str = GITHUB_TOKEN, token_ruff: str = GITHUB_TOKEN
124
+ ) -> StrDict:
125
+ dict_: StrDict = {"token-checkout": token_checkout, "token-ruff": token_ruff}
126
+ return {"name": "Run 'ruff'", "uses": "dycw/action-ruff@latest", "with": dict_}
127
+
128
+
129
+ def run_action_tag_dict(
130
+ *,
131
+ token_checkout: str = GITHUB_TOKEN,
132
+ token_uv: str = GITHUB_TOKEN,
133
+ user_name: str = "github-actions-bot",
134
+ user_email: str = "noreply@github.com",
135
+ major_minor: bool = False,
136
+ major: bool = False,
137
+ latest: bool = False,
138
+ ) -> StrDict:
139
+ dict_: StrDict = {
140
+ "token-checkout": token_checkout,
141
+ "token-uv": token_uv,
142
+ "user-name": user_name,
143
+ "user-email": user_email,
144
+ }
145
+ _add_boolean(dict_, "major-minor", value=major_minor)
146
+ _add_boolean(dict_, "major", value=major)
147
+ _add_boolean(dict_, "latest", value=latest)
148
+ return {
149
+ "name": "Tag latest commit",
150
+ "uses": "dycw/action-tag@latest",
151
+ "with": dict_,
152
+ }
153
+
154
+
155
+ def _add_boolean(dict_: StrDict, key: str, /, *, value: bool = False) -> None:
156
+ if value:
157
+ dict_[key] = value
158
+
159
+
160
+ def _add_item(dict_: StrDict, key: str, /, *, value: Any | None = None) -> None:
161
+ if value is not None:
162
+ dict_[key] = value
163
+
164
+
165
+ def _add_native_tls(dict_: StrDict, /, *, native_tls: bool = False) -> None:
166
+ _add_boolean(dict_, "native-tls", value=native_tls)
167
+
168
+
169
+ def _add_python_version(
170
+ dict_: StrDict, /, *, python_version: str | None = None
171
+ ) -> None:
172
+ _add_item(dict_, "python-version", value=python_version)
173
+
174
+
175
+ def _add_with_requirements(
176
+ dict_: StrDict, /, *, with_requirements: str | None = None
177
+ ) -> None:
178
+ _add_item(dict_, "with-requirements", value=with_requirements)
179
+
180
+
181
+ __all__ = [
182
+ "run_action_pre_commit_dict",
183
+ "run_action_publish_dict",
184
+ "run_action_pyright_dict",
185
+ "run_action_pytest_dict",
186
+ "run_action_random_sleep_dict",
187
+ "run_action_ruff_dict",
188
+ "run_action_tag_dict",
189
+ ]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
actions/hooks/cli.py ADDED
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from rich.pretty import pretty_repr
4
+ from typed_settings import click_options
5
+ from utilities.logging import basic_config
6
+ from utilities.os import is_pytest
7
+ from utilities.text import strip_and_dedent
8
+
9
+ from actions import __version__
10
+ from actions.hooks.lib import run_hooks
11
+ from actions.hooks.settings import HooksSettings
12
+ from actions.logging import LOGGER
13
+ from actions.settings import CommonSettings
14
+ from actions.utilities import LOADER
15
+
16
+
17
+ @click_options(CommonSettings, [LOADER], show_envvars_in_help=True, argname="common")
18
+ @click_options(HooksSettings, [LOADER], show_envvars_in_help=True, argname="hooks")
19
+ def hooks_sub_cmd(*, common: CommonSettings, hooks: HooksSettings) -> None:
20
+ if is_pytest():
21
+ return
22
+ basic_config(obj=LOGGER)
23
+ LOGGER.info(
24
+ strip_and_dedent("""
25
+ Running '%s' (version %s) with settings:
26
+ %s
27
+ %s
28
+ """),
29
+ run_hooks.__name__,
30
+ __version__,
31
+ pretty_repr(common),
32
+ pretty_repr(hooks),
33
+ )
34
+ run_hooks(repos=hooks.repos, hooks=hooks.hooks, sleep=hooks.sleep)
35
+
36
+
37
+ __all__ = ["hooks_sub_cmd"]
actions/hooks/lib.py ADDED
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from pathlib import Path
5
+ from re import search
6
+ from subprocess import CalledProcessError
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from utilities.functions import ensure_class, ensure_str
10
+ from utilities.text import strip_and_dedent
11
+ from whenever import TimeDelta
12
+ from yaml import safe_load
13
+
14
+ from actions import __version__
15
+ from actions.hooks.settings import HOOKS_SETTINGS
16
+ from actions.logging import LOGGER
17
+ from actions.utilities import log_run
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Iterator
21
+
22
+
23
+ def run_hooks(
24
+ *,
25
+ repos: list[str] | None = HOOKS_SETTINGS.repos,
26
+ hooks: list[str] | None = HOOKS_SETTINGS.hooks,
27
+ sleep: int = HOOKS_SETTINGS.sleep,
28
+ ) -> None:
29
+ LOGGER.info(
30
+ strip_and_dedent("""
31
+ Running '%s' (version %s) with settings:
32
+ - repos = %s
33
+ - hooks = %s
34
+ - sleep = %d
35
+ """),
36
+ run_hooks.__name__,
37
+ __version__,
38
+ repos,
39
+ hooks,
40
+ sleep,
41
+ )
42
+ results = {
43
+ hook: _run_hook(hook, sleep=sleep)
44
+ for hook in _yield_hooks(repos=repos, hooks=hooks)
45
+ }
46
+ failed = {hook: result for hook, result in results.items() if not result}
47
+ if len(failed) >= 1:
48
+ msg = f"Failed hook(s): {', '.join(failed)}"
49
+ raise RuntimeError(msg)
50
+
51
+
52
+ def _yield_hooks(
53
+ *,
54
+ repos: list[str] | None = HOOKS_SETTINGS.repos,
55
+ hooks: list[str] | None = HOOKS_SETTINGS.hooks,
56
+ ) -> Iterator[str]:
57
+ dict_ = safe_load(Path(".pre-commit-config.yaml").read_text())
58
+ repos_list = ensure_class(dict_["repos"], list)
59
+ results: set[str] = set()
60
+ for repo in (ensure_class(r, dict) for r in repos_list):
61
+ url = repo["repo"]
62
+ if (repos is not None) and any(search(repo_i, url) for repo_i in repos):
63
+ results.update(_yield_repo_hooks(repo))
64
+ elif hooks is not None:
65
+ for hook in _yield_repo_hooks(repo):
66
+ if any(search(hook_i, hook) for hook_i in hooks):
67
+ results.add(hook)
68
+ yield from sorted(results)
69
+
70
+
71
+ def _yield_repo_hooks(repo: dict[str, Any], /) -> Iterator[str]:
72
+ hooks = ensure_class(repo["hooks"], list)
73
+ for hook in (ensure_class(r, dict) for r in hooks):
74
+ yield ensure_str(hook["id"])
75
+
76
+
77
+ def _run_hook(hook: str, /, *, sleep: int = HOOKS_SETTINGS.sleep) -> bool:
78
+ LOGGER.info("Running '%s'...", hook)
79
+ try:
80
+ log_run("pre-commit", "run", "--verbose", "--all-files", hook, print=True)
81
+ except CalledProcessError:
82
+ is_success = False
83
+ else:
84
+ is_success = True
85
+ delta = TimeDelta(seconds=sleep)
86
+ LOGGER.info(
87
+ "Hook '%s' %s; sleeping for %s...",
88
+ hook,
89
+ "succeeded" if is_success else "failed",
90
+ delta,
91
+ )
92
+ time.sleep(sleep)
93
+ LOGGER.info("Finished sleeping for %s", delta)
94
+ return is_success
95
+
96
+
97
+ __all__ = ["run_hooks"]
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typed_settings import load_settings, option, settings
4
+
5
+ from actions.utilities import LOADER, convert_list_strs
6
+
7
+
8
+ @settings
9
+ class HooksSettings:
10
+ repos: list[str] | None = option(
11
+ default=None,
12
+ converter=convert_list_strs,
13
+ help="The repos whose hooks are to be run",
14
+ )
15
+ hooks: list[str] | None = option(
16
+ default=None, converter=convert_list_strs, help="The hooks to be run"
17
+ )
18
+ sleep: int = option(default=1, help="Sleep in between runs")
19
+
20
+
21
+ HOOKS_SETTINGS = load_settings(HooksSettings, [LOADER])
22
+
23
+
24
+ __all__ = ["HOOKS_SETTINGS", "HooksSettings"]
actions/logging.py ADDED
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from logging import getLogger
4
+
5
+ LOGGER = getLogger("actions")
6
+
7
+
8
+ __all__ = ["LOGGER"]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
actions/publish/cli.py ADDED
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from rich.pretty import pretty_repr
4
+ from typed_settings import click_options
5
+ from utilities.logging import basic_config
6
+ from utilities.os import is_pytest
7
+ from utilities.text import strip_and_dedent
8
+
9
+ from actions import __version__
10
+ from actions.logging import LOGGER
11
+ from actions.publish.lib import publish_package
12
+ from actions.publish.settings import PublishSettings
13
+ from actions.settings import CommonSettings
14
+ from actions.utilities import LOADER
15
+
16
+
17
+ @click_options(CommonSettings, [LOADER], show_envvars_in_help=True, argname="common")
18
+ @click_options(PublishSettings, [LOADER], show_envvars_in_help=True, argname="publish")
19
+ def publish_sub_cmd(*, common: CommonSettings, publish: PublishSettings) -> None:
20
+ if is_pytest():
21
+ return
22
+ basic_config(obj=LOGGER)
23
+ LOGGER.info(
24
+ strip_and_dedent("""
25
+ Running '%s' (version %s) with settings:
26
+ %s
27
+ %s
28
+ """),
29
+ publish_package.__name__,
30
+ __version__,
31
+ pretty_repr(common),
32
+ pretty_repr(publish),
33
+ )
34
+ publish_package(
35
+ username=publish.username,
36
+ password=publish.password,
37
+ publish_url=publish.publish_url,
38
+ trusted_publishing=publish.trusted_publishing,
39
+ native_tls=publish.native_tls,
40
+ )
41
+
42
+
43
+ __all__ = ["publish_sub_cmd"]
actions/publish/lib.py ADDED
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from utilities.tempfile import TemporaryDirectory
6
+ from utilities.text import strip_and_dedent
7
+
8
+ from actions import __version__
9
+ from actions.logging import LOGGER
10
+ from actions.publish.settings import PUBLISH_SETTINGS
11
+ from actions.utilities import log_run
12
+
13
+ if TYPE_CHECKING:
14
+ from typed_settings import Secret
15
+
16
+
17
+ def publish_package(
18
+ *,
19
+ username: str | None = PUBLISH_SETTINGS.username,
20
+ password: Secret[str] | None = PUBLISH_SETTINGS.password,
21
+ publish_url: str | None = PUBLISH_SETTINGS.publish_url,
22
+ trusted_publishing: bool = PUBLISH_SETTINGS.trusted_publishing,
23
+ native_tls: bool = PUBLISH_SETTINGS.native_tls,
24
+ ) -> None:
25
+ LOGGER.info(
26
+ strip_and_dedent("""
27
+ Running '%s' (version %s) with settings:
28
+ - username = %s
29
+ - password = %s
30
+ - publish_url = %s
31
+ - trusted_publishing = %s
32
+ - native_tls = %s
33
+ """),
34
+ publish_package.__name__,
35
+ __version__,
36
+ username,
37
+ password,
38
+ publish_url,
39
+ trusted_publishing,
40
+ native_tls,
41
+ )
42
+ with TemporaryDirectory() as temp:
43
+ log_run("uv", "build", "--out-dir", str(temp), "--wheel", "--clear")
44
+ log_run(
45
+ "uv",
46
+ "publish",
47
+ *([] if username is None else ["--username", username]),
48
+ *([] if password is None else ["--password", password]),
49
+ *([] if publish_url is None else ["--publish-url", publish_url]),
50
+ *(["--trusted-publishing", "always"] if trusted_publishing else []),
51
+ *(["--native-tls"] if native_tls else []),
52
+ f"{temp}/*",
53
+ )
54
+
55
+
56
+ __all__ = ["publish_package"]
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from typed_settings import Secret, load_settings, option, secret, settings
4
+
5
+ from actions.utilities import LOADER, convert_secret_str, convert_str
6
+
7
+
8
+ @settings
9
+ class PublishSettings:
10
+ username: str | None = option(
11
+ default=None, converter=convert_str, help="The username of the upload"
12
+ )
13
+ password: Secret[str] | None = secret(
14
+ default=None, converter=convert_secret_str, help="The password for the upload"
15
+ )
16
+ publish_url: str | None = option(
17
+ default=None, converter=convert_str, help="The URL of the upload endpoint"
18
+ )
19
+ trusted_publishing: bool = option(
20
+ default=False, help="Configure trusted publishing"
21
+ )
22
+ native_tls: bool = option(
23
+ default=False,
24
+ help="Whether to load TLS certificates from the platform's native certificate store",
25
+ )
26
+
27
+
28
+ PUBLISH_SETTINGS = load_settings(PublishSettings, [LOADER])
29
+
30
+
31
+ __all__ = ["PUBLISH_SETTINGS", "PublishSettings"]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import click
6
+ from click import argument
7
+ from utilities.logging import basic_config
8
+ from utilities.os import is_pytest
9
+ from utilities.text import strip_and_dedent
10
+
11
+ from actions import __version__
12
+ from actions.logging import LOGGER
13
+ from actions.requirements.lib import format_requirements
14
+
15
+
16
+ @argument(
17
+ "paths",
18
+ nargs=-1,
19
+ type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
20
+ )
21
+ def requirements_sub_cmd(*, paths: tuple[Path, ...]) -> None:
22
+ if is_pytest():
23
+ return
24
+ basic_config(obj=LOGGER)
25
+ LOGGER.info(
26
+ strip_and_dedent("""
27
+ Running '%s' (version %s) with settings:
28
+ - paths = %s
29
+ """),
30
+ format_requirements.__name__,
31
+ __version__,
32
+ paths,
33
+ )
34
+ format_requirements(*paths)
35
+
36
+
37
+ __all__ = ["requirements_sub_cmd"]
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, Any, override
6
+
7
+ from packaging._tokenizer import ParserSyntaxError
8
+ from packaging.requirements import InvalidRequirement, Requirement, _parse_requirement
9
+ from packaging.specifiers import Specifier, SpecifierSet
10
+ from tomlkit import TOMLDocument, array, dumps, loads, string
11
+ from tomlkit.items import Array, Table
12
+ from utilities.text import repr_str, strip_and_dedent
13
+
14
+ from actions import __version__
15
+ from actions.logging import LOGGER
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Iterator
19
+
20
+ from utilities.types import PathLike
21
+
22
+
23
+ _MODIFICATIONS: set[Path] = set()
24
+
25
+
26
+ def format_requirements(*paths: PathLike) -> None:
27
+ LOGGER.info(
28
+ strip_and_dedent("""
29
+ Running '%s' (version %s) with settings:
30
+ - paths = %s
31
+ """),
32
+ format_requirements.__name__,
33
+ __version__,
34
+ paths,
35
+ )
36
+ for path in paths:
37
+ _format_path(path)
38
+ if len(_MODIFICATIONS) >= 1:
39
+ LOGGER.info(
40
+ "Exiting due to modifications: %s",
41
+ ", ".join(map(repr_str, sorted(_MODIFICATIONS))),
42
+ )
43
+ sys.exit(1)
44
+
45
+
46
+ def _format_path(path: PathLike, /) -> None:
47
+ path = Path(path)
48
+ current = loads(path.read_text())
49
+ expected = _get_formatted(path)
50
+ is_equal = current == expected # tomlkit cannot handle !=
51
+ if not is_equal:
52
+ _ = path.write_text(dumps(expected).rstrip("\n") + "\n")
53
+ _MODIFICATIONS.add(path)
54
+
55
+
56
+ def _get_formatted(path: PathLike, /) -> TOMLDocument:
57
+ path = Path(path)
58
+ doc = loads(path.read_text())
59
+ if isinstance(dep_grps := doc.get("dependency-groups"), Table):
60
+ for key, value in dep_grps.items():
61
+ if isinstance(value, Array):
62
+ dep_grps[key] = _format_array(value)
63
+ if isinstance(project := doc["project"], Table):
64
+ if isinstance(deps := project["dependencies"], Array):
65
+ project["dependencies"] = _format_array(deps)
66
+ if isinstance(optional := project.get("optional-dependencies"), Table):
67
+ for key, value in optional.items():
68
+ if isinstance(value, Array):
69
+ optional[key] = _format_array(value)
70
+ return doc
71
+
72
+
73
+ def _format_array(dependencies: Array, /) -> Array:
74
+ new = array().multiline(multiline=True)
75
+ new.extend(map(_format_item, dependencies))
76
+ return new
77
+
78
+
79
+ def _format_item(item: Any, /) -> Any:
80
+ if not isinstance(item, str):
81
+ return item
82
+ return string(str(_CustomRequirement(item)))
83
+
84
+
85
+ class _CustomRequirement(Requirement):
86
+ @override
87
+ def __init__(self, requirement_string: str) -> None:
88
+ super().__init__(requirement_string)
89
+ try:
90
+ parsed = _parse_requirement(requirement_string)
91
+ except ParserSyntaxError as e:
92
+ raise InvalidRequirement(str(e)) from e
93
+ self.specifier = _CustomSpecifierSet(parsed.specifier)
94
+
95
+ @override
96
+ def _iter_parts(self, name: str) -> Iterator[str]:
97
+ yield name
98
+ if self.extras:
99
+ formatted_extras = ",".join(sorted(self.extras))
100
+ yield f"[{formatted_extras}]"
101
+ if self.specifier:
102
+ yield f" {self.specifier}"
103
+ if self.url:
104
+ yield f"@ {self.url}"
105
+ if self.marker:
106
+ yield " "
107
+ if self.marker:
108
+ yield f"; {self.marker}"
109
+
110
+
111
+ class _CustomSpecifierSet(SpecifierSet):
112
+ @override
113
+ def __str__(self) -> str:
114
+ specs = sorted(self._specs, key=self._key)
115
+ return ", ".join(map(str, specs))
116
+
117
+ def _key(self, spec: Specifier, /) -> int:
118
+ return [">=", "<"].index(spec.operator)
119
+
120
+
121
+ __all__ = ["format_requirements"]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import click
6
+ from click import argument
7
+ from utilities.logging import basic_config
8
+ from utilities.os import is_pytest
9
+ from utilities.text import strip_and_dedent
10
+
11
+ from actions import __version__
12
+ from actions.logging import LOGGER
13
+ from actions.sequence_strs.lib import replace_sequence_strs
14
+
15
+
16
+ @argument(
17
+ "paths",
18
+ nargs=-1,
19
+ type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
20
+ )
21
+ def sequence_strs_sub_cmd(*, paths: tuple[Path, ...]) -> None:
22
+ if is_pytest():
23
+ return
24
+ basic_config(obj=LOGGER)
25
+ LOGGER.info(
26
+ strip_and_dedent("""
27
+ Running '%s' (version %s) with settings:
28
+ - paths = %s
29
+ """),
30
+ replace_sequence_strs.__name__,
31
+ __version__,
32
+ paths,
33
+ )
34
+ replace_sequence_strs(*paths)
35
+
36
+
37
+ __all__ = ["sequence_strs_sub_cmd"]
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, override
6
+
7
+ from libcst import CSTTransformer, Module, Name, Subscript, parse_module
8
+ from libcst.matchers import Index as MIndex
9
+ from libcst.matchers import Name as MName
10
+ from libcst.matchers import Subscript as MSubscript
11
+ from libcst.matchers import SubscriptElement as MSubscriptElement
12
+ from libcst.matchers import matches
13
+ from libcst.metadata import MetadataWrapper
14
+ from utilities.text import repr_str, strip_and_dedent
15
+
16
+ from actions import __version__
17
+ from actions.logging import LOGGER
18
+
19
+ if TYPE_CHECKING:
20
+ from utilities.types import PathLike
21
+
22
+
23
+ _MODIFICATIONS: set[Path] = set()
24
+
25
+
26
+ def replace_sequence_strs(*paths: PathLike) -> None:
27
+ LOGGER.info(
28
+ strip_and_dedent("""
29
+ Running '%s' (version %s) with settings:
30
+ - paths = %s
31
+ """),
32
+ replace_sequence_strs.__name__,
33
+ __version__,
34
+ paths,
35
+ )
36
+ for path in paths:
37
+ _format_path(path)
38
+ if len(_MODIFICATIONS) >= 1:
39
+ LOGGER.info(
40
+ "Exiting due to modifications: %s",
41
+ ", ".join(map(repr_str, sorted(_MODIFICATIONS))),
42
+ )
43
+ sys.exit(1)
44
+
45
+
46
+ def _format_path(path: PathLike, /) -> None:
47
+ path = Path(path)
48
+ current = parse_module(path.read_text())
49
+ expected = _get_formatted(path)
50
+ if current.code != expected.code:
51
+ _ = path.write_text(expected.code.rstrip("\n") + "\n")
52
+ _MODIFICATIONS.add(path)
53
+
54
+
55
+ def _get_formatted(path: PathLike, /) -> Module:
56
+ path = Path(path)
57
+ existing = path.read_text()
58
+ wrapper = MetadataWrapper(parse_module(existing))
59
+ return wrapper.module.visit(SequenceToListTransformer())
60
+
61
+
62
+ class SequenceToListTransformer(CSTTransformer):
63
+ @override
64
+ def leave_Subscript(
65
+ self, original_node: Subscript, updated_node: Subscript
66
+ ) -> Subscript:
67
+ _ = original_node
68
+ if matches(
69
+ updated_node,
70
+ MSubscript(
71
+ value=MName("Sequence"),
72
+ slice=[MSubscriptElement(slice=MIndex(value=MName("str")))],
73
+ ),
74
+ ):
75
+ return updated_node.with_changes(value=Name("list"))
76
+ return updated_node
77
+
78
+
79
+ __all__ = ["replace_sequence_strs"]
actions/settings.py ADDED
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ from typed_settings import Secret, load_settings, secret, settings
4
+
5
+ from actions.utilities import LOADER, convert_secret_str
6
+
7
+
8
+ @settings
9
+ class CommonSettings:
10
+ token: Secret[str] | None = secret(
11
+ default=None, converter=convert_secret_str, help="GitHub token"
12
+ )
13
+
14
+
15
+ COMMON_SETTINGS = load_settings(CommonSettings, [LOADER])
16
+
17
+
18
+ __all__ = ["COMMON_SETTINGS", "CommonSettings"]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
actions/sleep/cli.py ADDED
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from rich.pretty import pretty_repr
4
+ from typed_settings import click_options
5
+ from utilities.logging import basic_config
6
+ from utilities.os import is_pytest
7
+ from utilities.text import strip_and_dedent
8
+
9
+ from actions import __version__
10
+ from actions.logging import LOGGER
11
+ from actions.settings import CommonSettings
12
+ from actions.sleep.lib import random_sleep
13
+ from actions.sleep.settings import SleepSettings
14
+ from actions.utilities import LOADER
15
+
16
+
17
+ @click_options(CommonSettings, [LOADER], show_envvars_in_help=True, argname="common")
18
+ @click_options(SleepSettings, [LOADER], show_envvars_in_help=True, argname="sleep")
19
+ def sleep_sub_cmd(*, common: CommonSettings, sleep: SleepSettings) -> None:
20
+ if is_pytest():
21
+ return
22
+ basic_config(obj=LOGGER)
23
+ LOGGER.info(
24
+ strip_and_dedent("""
25
+ Running '%s' (version %s) with settings:
26
+ %s
27
+ %s
28
+ """),
29
+ random_sleep.__name__,
30
+ __version__,
31
+ pretty_repr(common),
32
+ pretty_repr(sleep),
33
+ )
34
+ random_sleep(
35
+ min_=sleep.min, max_=sleep.max, step=sleep.step, log_freq=sleep.log_freq
36
+ )
37
+
38
+
39
+ __all__ = ["sleep_sub_cmd"]
actions/sleep/lib.py ADDED
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ from math import ceil, floor
4
+ from random import choice
5
+ from time import sleep
6
+
7
+ from utilities.text import strip_and_dedent
8
+ from utilities.whenever import get_now
9
+ from whenever import TimeDelta, ZonedDateTime
10
+
11
+ from actions import __version__
12
+ from actions.logging import LOGGER
13
+ from actions.sleep.settings import SLEEP_SETTINGS
14
+
15
+
16
+ def random_sleep(
17
+ *,
18
+ min_: int = SLEEP_SETTINGS.min,
19
+ max_: int = SLEEP_SETTINGS.max,
20
+ step: int = SLEEP_SETTINGS.step,
21
+ log_freq: int = SLEEP_SETTINGS.log_freq,
22
+ ) -> None:
23
+ LOGGER.info(
24
+ strip_and_dedent("""
25
+ Running '%s' (version %s) with settings:
26
+ - min_ = %s
27
+ - max_ = %s
28
+ - step = %s
29
+ - log_freq = %s
30
+ """),
31
+ random_sleep.__name__,
32
+ __version__,
33
+ min_,
34
+ max_,
35
+ step,
36
+ log_freq,
37
+ )
38
+ start = get_now()
39
+ delta = TimeDelta(seconds=choice(range(min_, max_, step)))
40
+ LOGGER.info("Sleeping for %s...", delta)
41
+ end = (start + delta).round(mode="ceil")
42
+ while (now := get_now()) < end:
43
+ _intermediate(start, now, end, log_freq=log_freq)
44
+ LOGGER.info("Finished sleeping for %s", delta)
45
+
46
+
47
+ def _intermediate(
48
+ start: ZonedDateTime,
49
+ now: ZonedDateTime,
50
+ end: ZonedDateTime,
51
+ /,
52
+ *,
53
+ log_freq: int = SLEEP_SETTINGS.log_freq,
54
+ ) -> None:
55
+ elapsed = TimeDelta(seconds=floor((now - start).in_seconds()))
56
+ remaining = TimeDelta(seconds=ceil((end - now).in_seconds()))
57
+ this_sleep = min(remaining, TimeDelta(seconds=log_freq))
58
+ LOGGER.info(
59
+ "Sleeping for %s... (elapsed = %s, remaining = %s)",
60
+ this_sleep,
61
+ elapsed,
62
+ remaining,
63
+ )
64
+ sleep(round(this_sleep.in_seconds()))
65
+
66
+
67
+ __all__ = ["random_sleep"]
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from typed_settings import load_settings, option, settings
4
+
5
+ from actions.utilities import LOADER
6
+
7
+
8
+ @settings
9
+ class SleepSettings:
10
+ min: int = option(default=0, help="Minimum duration, in seconds")
11
+ max: int = option(default=3600, help="Maximum duration, in seconds")
12
+ step: int = option(default=1, help="Step duration, in seconds")
13
+ log_freq: int = option(default=60, help="Log frequency, in seconds")
14
+
15
+
16
+ SLEEP_SETTINGS = load_settings(SleepSettings, [LOADER])
17
+
18
+
19
+ __all__ = ["SLEEP_SETTINGS", "SleepSettings"]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
actions/tag/cli.py ADDED
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from rich.pretty import pretty_repr
4
+ from typed_settings import click_options
5
+ from utilities.logging import basic_config
6
+ from utilities.os import is_pytest
7
+ from utilities.text import strip_and_dedent
8
+
9
+ from actions import __version__
10
+ from actions.logging import LOGGER
11
+ from actions.settings import CommonSettings
12
+ from actions.tag.lib import tag_commit
13
+ from actions.tag.settings import TagSettings
14
+ from actions.utilities import LOADER
15
+
16
+
17
+ @click_options(CommonSettings, [LOADER], show_envvars_in_help=True, argname="common")
18
+ @click_options(TagSettings, [LOADER], show_envvars_in_help=True, argname="tag")
19
+ def tag_sub_cmd(*, common: CommonSettings, tag: TagSettings) -> None:
20
+ if is_pytest():
21
+ return
22
+ basic_config(obj=LOGGER)
23
+ LOGGER.info(
24
+ strip_and_dedent("""
25
+ Running '%s' (version %s) with settings:
26
+ %s
27
+ %s
28
+ """),
29
+ tag_commit.__name__,
30
+ __version__,
31
+ pretty_repr(common),
32
+ pretty_repr(tag),
33
+ )
34
+ tag_commit(
35
+ user_name=tag.user_name,
36
+ user_email=tag.user_email,
37
+ major_minor=tag.major_minor,
38
+ major=tag.major,
39
+ latest=tag.latest,
40
+ )
41
+
42
+
43
+ __all__ = ["tag_sub_cmd"]
actions/tag/lib.py ADDED
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import suppress
4
+ from subprocess import CalledProcessError
5
+
6
+ from utilities.text import strip_and_dedent
7
+ from utilities.version import parse_version
8
+
9
+ from actions import __version__
10
+ from actions.logging import LOGGER
11
+ from actions.tag.settings import TAG_SETTINGS
12
+ from actions.utilities import log_run
13
+
14
+
15
+ def tag_commit(
16
+ *,
17
+ user_name: str = TAG_SETTINGS.user_name,
18
+ user_email: str = TAG_SETTINGS.user_email,
19
+ major_minor: bool = TAG_SETTINGS.major_minor,
20
+ major: bool = TAG_SETTINGS.major,
21
+ latest: bool = TAG_SETTINGS.latest,
22
+ ) -> None:
23
+ LOGGER.info(
24
+ strip_and_dedent("""
25
+ Running '%s' (version %s) with settings:
26
+ - user_name = %s
27
+ - user_email = %s
28
+ - major_minor = %s
29
+ - major = %s
30
+ - latest = %s
31
+ """),
32
+ tag_commit.__name__,
33
+ __version__,
34
+ user_name,
35
+ user_email,
36
+ major_minor,
37
+ major,
38
+ latest,
39
+ )
40
+ log_run("git", "config", "--global", "user.name", user_name)
41
+ log_run("git", "config", "--global", "user.email", user_email)
42
+ version = parse_version(
43
+ log_run("bump-my-version", "show", "current_version", return_=True)
44
+ )
45
+ _tag(str(version))
46
+ if major_minor:
47
+ _tag(f"{version.major}.{version.minor}")
48
+ if major:
49
+ _tag(str(version.major))
50
+ if latest:
51
+ _tag("latest")
52
+
53
+
54
+ def _tag(version: str, /) -> None:
55
+ with suppress(CalledProcessError):
56
+ log_run("git", "tag", "--delete", version)
57
+ with suppress(CalledProcessError):
58
+ log_run("git", "push", "--delete", "origin", version)
59
+ log_run("git", "tag", "-a", version, "HEAD", "-m", version)
60
+ log_run("git", "push", "--tags", "--force", "--set-upstream", "origin")
61
+
62
+
63
+ __all__ = ["tag_commit"]
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from typed_settings import load_settings, option, settings
4
+
5
+ from actions.utilities import LOADER
6
+
7
+
8
+ @settings
9
+ class TagSettings:
10
+ user_name: str = option(default="github-actions-bot", help="'git' user name")
11
+ user_email: str = option(default="noreply@github.com", help="'git' user email")
12
+ major_minor: bool = option(default=False, help="Add the 'major.minor' tag")
13
+ major: bool = option(default=False, help="Add the 'major' tag")
14
+ latest: bool = option(default=False, help="Add the 'latest' tag")
15
+
16
+
17
+ TAG_SETTINGS = load_settings(TagSettings, [LOADER])
18
+
19
+
20
+ __all__ = ["TAG_SETTINGS", "TagSettings"]
actions/types.py ADDED
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from typed_settings import Secret
6
+
7
+ type SecretLike = str | Secret[str]
8
+ type StrDict = dict[str, Any]
9
+
10
+
11
+ __all__ = ["SecretLike", "StrDict"]
actions/utilities.py ADDED
@@ -0,0 +1,105 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Literal, assert_never, overload
4
+
5
+ from typed_settings import EnvLoader, Secret
6
+ from utilities.subprocess import run
7
+
8
+ from actions.logging import LOGGER
9
+
10
+ if TYPE_CHECKING:
11
+ from utilities.types import StrStrMapping
12
+
13
+ from actions.types import SecretLike
14
+
15
+
16
+ LOADER = EnvLoader("")
17
+
18
+
19
+ def convert_list_strs(
20
+ x: str | list[str] | tuple[str, ...] | None, /
21
+ ) -> list[str] | None:
22
+ match x:
23
+ case None:
24
+ return None
25
+ case list():
26
+ return x
27
+ case tuple():
28
+ return None if x == () else list(x)
29
+ case str():
30
+ return x.splitlines()
31
+ case never:
32
+ assert_never(never)
33
+
34
+
35
+ def convert_secret_str(x: SecretLike | None, /) -> Secret[str] | None:
36
+ empty = {None, ""}
37
+ match x:
38
+ case Secret():
39
+ return None if x.get_secret_value() in empty else x
40
+ case str():
41
+ return None if x in empty else Secret(x)
42
+ case None:
43
+ return None
44
+ case never:
45
+ assert_never(never)
46
+
47
+
48
+ def convert_str(x: str | None, /) -> str | None:
49
+ match x:
50
+ case str():
51
+ return None if x == "" else x
52
+ case None:
53
+ return None
54
+ case never:
55
+ assert_never(never)
56
+
57
+
58
+ @overload
59
+ def log_run(
60
+ cmd: SecretLike,
61
+ /,
62
+ *cmds: SecretLike,
63
+ env: StrStrMapping | None = None,
64
+ print: bool = False,
65
+ return_: Literal[True],
66
+ ) -> str: ...
67
+ @overload
68
+ def log_run(
69
+ cmd: SecretLike,
70
+ /,
71
+ *cmds: SecretLike,
72
+ env: StrStrMapping | None = None,
73
+ print: bool = False,
74
+ return_: Literal[False] = False,
75
+ ) -> None: ...
76
+ @overload
77
+ def log_run(
78
+ cmd: SecretLike,
79
+ /,
80
+ *cmds: SecretLike,
81
+ env: StrStrMapping | None = None,
82
+ print: bool = False,
83
+ return_: bool = False,
84
+ ) -> str | None: ...
85
+ def log_run(
86
+ cmd: SecretLike,
87
+ /,
88
+ *cmds: SecretLike,
89
+ env: StrStrMapping | None = None,
90
+ print: bool = False, # noqa: A002
91
+ return_: bool = False,
92
+ ) -> str | None:
93
+ all_cmds = [cmd, *cmds]
94
+ LOGGER.info("Running '%s'...", " ".join(map(str, all_cmds)))
95
+ unwrapped = [c if isinstance(c, str) else c.get_secret_value() for c in all_cmds]
96
+ return run(*unwrapped, env=env, print=print, return_=return_, logger=LOGGER)
97
+
98
+
99
+ __all__ = [
100
+ "LOADER",
101
+ "convert_list_strs",
102
+ "convert_secret_str",
103
+ "convert_str",
104
+ "log_run",
105
+ ]
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.3
2
+ Name: dycw-actions
3
+ Version: 0.3.40
4
+ Summary: GitHub actions
5
+ Requires-Dist: click>=8.3.1,<9
6
+ Requires-Dist: dycw-utilities>=0.176.1,<1
7
+ Requires-Dist: libcst>=1.8.6,<2
8
+ Requires-Dist: packaging>=25.0,<26
9
+ Requires-Dist: pyyaml>=6.0.3,<7
10
+ Requires-Dist: rich>=14.2.0,<15
11
+ Requires-Dist: tomlkit>=0.13.3,<1
12
+ Requires-Dist: typed-settings[attrs,click]>=25.3.0,<26
13
+ Requires-Python: >=3.12
14
+ Description-Content-Type: text/markdown
15
+
16
+ # `actions`
17
+
18
+ GitHub actions
@@ -0,0 +1,35 @@
1
+ actions/__init__.py,sha256=lw2DhpYl61GrPl9-Oi7xYfsHKXdE7tIgKWgLrQAcRQg,59
2
+ actions/cli.py,sha256=SnlIyfHLjC8uZ6yLhg4S15nqOf9TT81J74rZOqvyqiM,929
3
+ actions/conformalize/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
4
+ actions/conformalize/defaults.py,sha256=tYYLr3aRRRvnR6NSADOJMN-B6gtqg2A9gBuheEPyGy4,189
5
+ actions/conformalize/dicts.py,sha256=_l_3y-07bjDq7n8xQgsykrwZ6dEFFIzm0_P3PZMcaRo,5630
6
+ actions/hooks/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
7
+ actions/hooks/cli.py,sha256=xV-2TgrDUhkKYbLRVtCglYCu8uWmh0leiYnob66rS5k,1141
8
+ actions/hooks/lib.py,sha256=QNszNiQKzdao-mOWfmwFLR9KaGsUTv46oPKqu3oCt8E,2882
9
+ actions/hooks/settings.py,sha256=gWuBgU5vdN98LM_t5qzJ9cra64M0qA3ULpQ7hmXGNS4,633
10
+ actions/logging.py,sha256=rMTcQMGndUHTCaXXtyOHt_VXUMhQGRHoN7okpoX0y5I,120
11
+ actions/publish/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
12
+ actions/publish/cli.py,sha256=OEJSe5dYW06Mmf5JzAthUOFm8K5ZbkwAsUcP6L1kLa0,1334
13
+ actions/publish/lib.py,sha256=0zkNFWGqJcC-2v-3YXWdhO4VcQCX9BCo6uKIOvAFLUw,1785
14
+ actions/publish/settings.py,sha256=mVu2jjQQyFH9TDsh30dscSSB1LlxY493mxRiw31mgQo,972
15
+ actions/requirements/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
16
+ actions/requirements/cli.py,sha256=I4Nd14TYnoA2iWbreQyiGcm4NSpstLIRFkOHO2ymX0M,887
17
+ actions/requirements/lib.py,sha256=ovZAolTQQAAMVabBxyWNuY13ENp8-vRnRt1ymFurVhQ,3709
18
+ actions/sequence_strs/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
19
+ actions/sequence_strs/cli.py,sha256=YMC8jNXfHjASwkIca3rED8FeE-jgGv5ek04F7G2bIJA,896
20
+ actions/sequence_strs/lib.py,sha256=F1YFX9nrcMWnfvDtnx4jEXIHiX7LgB3vQGfjOQsjKXA,2260
21
+ actions/settings.py,sha256=D-bSBoDn183sIs2ejSHGUXps3HLHuUXYbnbJ3ZHWFg8,423
22
+ actions/sleep/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
23
+ actions/sleep/cli.py,sha256=8fw8cr8BzaymJ87appXwk8ntrxqTUtMVxGNchS4qHjo,1181
24
+ actions/sleep/lib.py,sha256=y_Sxjv_KD_pamHyQySF96YQ5ETA-wHHKOOJq6OD8ub4,1791
25
+ actions/sleep/settings.py,sha256=1hMd5mUsFb6gLajdAZ8bvFYjDhE1BWiPSKOYiHJEvqM,556
26
+ actions/tag/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
27
+ actions/tag/cli.py,sha256=itaa71bML6zb91jBDei0npLclIoh8ZKbvFWNzMNRR1g,1231
28
+ actions/tag/lib.py,sha256=3pyhj-b81NWVRGSzoeNkCk8fcrtCjcWss5bMr9xFEeU,1842
29
+ actions/tag/settings.py,sha256=s6QBI4bZFjk2mIPKIlyb3sLET5ZpXsTb-2bzjW9m5Ew,646
30
+ actions/types.py,sha256=azBTLx4DER-Sd1qGCmbJSFs3uzNe9w-7iyKwT2h6iOk,199
31
+ actions/utilities.py,sha256=o-OKP9EbeQ2yG4tTM1eHIRhtF928n7E6YjJQPlbmtIQ,2426
32
+ dycw_actions-0.3.40.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
33
+ dycw_actions-0.3.40.dist-info/entry_points.txt,sha256=2Uu7wAZOm0mmcsGBEsGB370HAWgVWECRFJ9rKgfC3-I,46
34
+ dycw_actions-0.3.40.dist-info/METADATA,sha256=6WAaMT5QZbVNsKM6cGNexpqJuDetmlAMc-FQUq4uhn4,467
35
+ dycw_actions-0.3.40.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.21
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ action = actions.cli:_main
3
+