dycw-actions 0.3.2__py3-none-any.whl → 0.6.4__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 (59) hide show
  1. actions/__init__.py +1 -1
  2. actions/action_dicts/constants.py +8 -0
  3. actions/action_dicts/lib.py +186 -0
  4. actions/clean_dir/cli.py +33 -0
  5. actions/clean_dir/lib.py +59 -0
  6. actions/clean_dir/settings.py +18 -0
  7. actions/cli.py +44 -6
  8. actions/conformalize_repo/cli.py +76 -0
  9. actions/conformalize_repo/configs/gitignore +244 -0
  10. actions/conformalize_repo/constants.py +72 -0
  11. actions/conformalize_repo/lib.py +1522 -0
  12. actions/conformalize_repo/settings.py +119 -0
  13. actions/constants.py +10 -0
  14. actions/format_requirements/__init__.py +1 -0
  15. actions/format_requirements/cli.py +37 -0
  16. actions/format_requirements/lib.py +121 -0
  17. actions/publish_package/__init__.py +1 -0
  18. actions/{publish → publish_package}/cli.py +6 -10
  19. actions/publish_package/doc.py +6 -0
  20. actions/{publish → publish_package}/lib.py +17 -16
  21. actions/{publish → publish_package}/settings.py +7 -7
  22. actions/random_sleep/__init__.py +1 -0
  23. actions/{sleep → random_sleep}/cli.py +6 -10
  24. actions/random_sleep/doc.py +6 -0
  25. actions/{sleep → random_sleep}/lib.py +14 -13
  26. actions/{sleep → random_sleep}/settings.py +3 -3
  27. actions/replace_sequence_strs/__init__.py +1 -0
  28. actions/replace_sequence_strs/cli.py +37 -0
  29. actions/replace_sequence_strs/lib.py +79 -0
  30. actions/run_hooks/__init__.py +1 -0
  31. actions/run_hooks/cli.py +33 -0
  32. actions/run_hooks/doc.py +6 -0
  33. actions/run_hooks/lib.py +97 -0
  34. actions/run_hooks/settings.py +24 -0
  35. actions/setup_cronjob/__init__.py +1 -0
  36. actions/setup_cronjob/cli.py +43 -0
  37. actions/setup_cronjob/configs/cron.tmpl +3 -0
  38. actions/setup_cronjob/configs/logrotate.tmpl +10 -0
  39. actions/setup_cronjob/constants.py +8 -0
  40. actions/setup_cronjob/lib.py +120 -0
  41. actions/setup_cronjob/settings.py +27 -0
  42. actions/tag_commit/__init__.py +1 -0
  43. actions/{tag → tag_commit}/cli.py +6 -10
  44. actions/tag_commit/doc.py +6 -0
  45. actions/tag_commit/lib.py +63 -0
  46. actions/{tag → tag_commit}/settings.py +3 -3
  47. actions/types.py +11 -1
  48. actions/utilities.py +68 -8
  49. dycw_actions-0.6.4.dist-info/METADATA +21 -0
  50. dycw_actions-0.6.4.dist-info/RECORD +56 -0
  51. {dycw_actions-0.3.2.dist-info → dycw_actions-0.6.4.dist-info}/WHEEL +1 -1
  52. actions/settings.py +0 -18
  53. actions/tag/lib.py +0 -62
  54. dycw_actions-0.3.2.dist-info/METADATA +0 -14
  55. dycw_actions-0.3.2.dist-info/RECORD +0 -22
  56. /actions/{publish → action_dicts}/__init__.py +0 -0
  57. /actions/{sleep → clean_dir}/__init__.py +0 -0
  58. /actions/{tag → conformalize_repo}/__init__.py +0 -0
  59. {dycw_actions-0.3.2.dist-info → dycw_actions-0.6.4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ from typed_settings import load_settings, option, settings
4
+
5
+ from actions.conformalize_repo.constants import RUN_VERSION_BUMP
6
+ from actions.utilities import LOADER
7
+
8
+
9
+ @settings
10
+ class Settings:
11
+ coverage: bool = option(default=False, help="Set up '.coveragerc.toml'")
12
+ description: str | None = option(default=None, help="Repo description")
13
+ envrc: bool = option(default=False, help="Set up '.envrc'")
14
+ envrc__uv: bool = option(default=False, help="Set up '.envrc' with uv")
15
+ envrc__uv__native_tls: bool = option(
16
+ default=False, help="Set up '.envrc' with uv native TLS"
17
+ )
18
+ github__pull_request__pre_commit: bool = option(
19
+ default=False, help="Set up 'pull-request.yaml' pre-commit"
20
+ )
21
+ github__pull_request__pre_commit__gitea: bool = option(
22
+ default=False, help="Set up 'pull-request.yaml' for Gitea"
23
+ )
24
+ github__pull_request__pyright: bool = option(
25
+ default=False, help="Set up 'pull-request.yaml' pyright"
26
+ )
27
+ github__pull_request__pytest__macos: bool = option(
28
+ default=False, help="Set up 'pull-request.yaml' pytest with MacOS"
29
+ )
30
+ github__pull_request__pytest__ubuntu: bool = option(
31
+ default=False, help="Set up 'pull-request.yaml' pytest with Ubuntu"
32
+ )
33
+ github__pull_request__pytest__windows: bool = option(
34
+ default=False, help="Set up 'pull-request.yaml' pytest with Windows"
35
+ )
36
+ github__pull_request__ruff: bool = option(
37
+ default=False, help="Set up 'pull-request.yaml' ruff"
38
+ )
39
+ github__push__publish: bool = option(
40
+ default=False, help="Set up 'push.yaml' publishing"
41
+ )
42
+ github__push__tag: bool = option(default=False, help="Set up 'push.yaml' tagging")
43
+ github__push__tag__major: bool = option(
44
+ default=False, help="Set up 'push.yaml' with the 'major' tag"
45
+ )
46
+ github__push__tag__major_minor: bool = option(
47
+ default=False, help="Set up 'push.yaml' with the 'major.minor' tag"
48
+ )
49
+ github__push__tag__latest: bool = option(
50
+ default=False, help="Set up 'push.yaml' tagging"
51
+ )
52
+ gitignore: bool = option(default=False, help="Set up '.gitignore'")
53
+ package_name: str | None = option(default=None, help="Package name")
54
+ pre_commit__dockerfmt: bool = option(
55
+ default=False, help="Set up '.pre-commit-config.yaml' dockerfmt"
56
+ )
57
+ pre_commit__prettier: bool = option(
58
+ default=False, help="Set up '.pre-commit-config.yaml' prettier"
59
+ )
60
+ pre_commit__python: bool = option(
61
+ default=False, help="Set up '.pre-commit-config.yaml' python"
62
+ )
63
+ pre_commit__ruff: bool = option(
64
+ default=False, help="Set up '.pre-commit-config.yaml' ruff"
65
+ )
66
+ pre_commit__shell: bool = option(
67
+ default=False, help="Set up '.pre-commit-config.yaml' shell"
68
+ )
69
+ pre_commit__taplo: bool = option(
70
+ default=False, help="Set up '.pre-commit-config.yaml' taplo"
71
+ )
72
+ pre_commit__uv: bool = option(
73
+ default=False, help="Set up '.pre-commit-config.yaml' uv"
74
+ )
75
+ pre_commit__uv__script: str | None = option(
76
+ default=None, help="Set up '.pre-commit-config.yaml' uv lock script"
77
+ )
78
+ pyproject: bool = option(default=False, help="Set up 'pyproject.toml'")
79
+ pyproject__project__optional_dependencies__scripts: bool = option(
80
+ default=False,
81
+ help="Set up 'pyproject.toml' [project.optional-dependencies.scripts]",
82
+ )
83
+ pyproject__tool__uv__indexes: list[tuple[str, str]] = option(
84
+ factory=list, help="Set up 'pyproject.toml' [[uv.tool.index]]"
85
+ )
86
+ pyright: bool = option(default=False, help="Set up 'pyrightconfig.json'")
87
+ pytest: bool = option(default=False, help="Set up 'pytest.toml'")
88
+ pytest__asyncio: bool = option(default=False, help="Set up 'pytest.toml' asyncio_*")
89
+ pytest__ignore_warnings: bool = option(
90
+ default=False, help="Set up 'pytest.toml' filterwarnings"
91
+ )
92
+ pytest__timeout: int | None = option(
93
+ default=None, help="Set up 'pytest.toml' timeout"
94
+ )
95
+ python_package_name: str | None = option(
96
+ default=None, help="Python package name override"
97
+ )
98
+ python_version: str = option(default="3.14", help="Python version")
99
+ readme: bool = option(default=False, help="Set up 'README.md'")
100
+ repo_name: str | None = option(default=None, help="Repo name")
101
+ ruff: bool = option(default=False, help="Set up 'ruff.toml'")
102
+ run_version_bump: bool = option(default=RUN_VERSION_BUMP, help="Run version bump")
103
+ script: str | None = option(
104
+ default=None, help="Set up a script instead of a package"
105
+ )
106
+
107
+ @property
108
+ def python_package_name_use(self) -> str | None:
109
+ if self.python_package_name is not None:
110
+ return self.python_package_name
111
+ if self.package_name is not None:
112
+ return self.package_name.replace("-", "_")
113
+ return None
114
+
115
+
116
+ SETTINGS = load_settings(Settings, [LOADER])
117
+
118
+
119
+ __all__ = ["SETTINGS", "Settings"]
actions/constants.py ADDED
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ from ruamel.yaml import YAML
4
+ from utilities.importlib import files
5
+
6
+ PATH_ACTIONS = files(anchor="actions")
7
+ YAML_INSTANCE = YAML()
8
+
9
+
10
+ __all__ = ["PATH_ACTIONS", "YAML_INSTANCE"]
@@ -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.format_requirements.lib import format_requirements
13
+ from actions.logging import LOGGER
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 format_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__ = ["format_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
@@ -8,27 +8,23 @@ from utilities.text import strip_and_dedent
8
8
 
9
9
  from actions import __version__
10
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
11
+ from actions.publish_package.lib import publish_package
12
+ from actions.publish_package.settings import Settings
14
13
  from actions.utilities import LOADER
15
14
 
16
15
 
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:
16
+ @click_options(Settings, [LOADER], show_envvars_in_help=True, argname="publish")
17
+ def publish_package_sub_cmd(*, publish: Settings) -> None:
20
18
  if is_pytest():
21
19
  return
22
20
  basic_config(obj=LOGGER)
23
21
  LOGGER.info(
24
22
  strip_and_dedent("""
25
- Running '%r' (version %s) with settings:
26
- %s
23
+ Running '%s' (version %s) with settings:
27
24
  %s
28
25
  """),
29
26
  publish_package.__name__,
30
27
  __version__,
31
- pretty_repr(common),
32
28
  pretty_repr(publish),
33
29
  )
34
30
  publish_package(
@@ -40,4 +36,4 @@ def publish_sub_cmd(*, common: CommonSettings, publish: PublishSettings) -> None
40
36
  )
41
37
 
42
38
 
43
- __all__ = ["publish_sub_cmd"]
39
+ __all__ = ["publish_package_sub_cmd"]
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ DOCSTRING = "Build and publish the package"
4
+
5
+
6
+ __all__ = ["DOCSTRING"]
@@ -3,10 +3,11 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from utilities.tempfile import TemporaryDirectory
6
+ from utilities.text import strip_and_dedent
6
7
 
7
8
  from actions import __version__
8
9
  from actions.logging import LOGGER
9
- from actions.publish.settings import PUBLISH_SETTINGS
10
+ from actions.publish_package.settings import SETTINGS
10
11
  from actions.utilities import log_run
11
12
 
12
13
  if TYPE_CHECKING:
@@ -15,21 +16,21 @@ if TYPE_CHECKING:
15
16
 
16
17
  def publish_package(
17
18
  *,
18
- username: str | None = PUBLISH_SETTINGS.username,
19
- password: Secret[str] | None = PUBLISH_SETTINGS.password,
20
- publish_url: str | None = PUBLISH_SETTINGS.publish_url,
21
- trusted_publishing: bool = PUBLISH_SETTINGS.trusted_publishing,
22
- native_tls: bool = PUBLISH_SETTINGS.native_tls,
19
+ username: str | None = SETTINGS.username,
20
+ password: Secret[str] | None = SETTINGS.password,
21
+ publish_url: str | None = SETTINGS.publish_url,
22
+ trusted_publishing: bool = SETTINGS.trusted_publishing,
23
+ native_tls: bool = SETTINGS.native_tls,
23
24
  ) -> None:
24
25
  LOGGER.info(
25
- """\
26
- Running %r (version %s) with settings:
27
- - username = %s
28
- - password = %s
29
- - publish_url = %s
30
- - trusted_publishing = %s
31
- - native_tls = %s
32
- """,
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
+ """),
33
34
  publish_package.__name__,
34
35
  __version__,
35
36
  username,
@@ -39,8 +40,8 @@ Running %r (version %s) with settings:
39
40
  native_tls,
40
41
  )
41
42
  with TemporaryDirectory() as temp:
42
- _ = log_run("uv", "build", "--out-dir", str(temp), "--wheel", "--clear")
43
- _ = log_run(
43
+ log_run("uv", "build", "--out-dir", str(temp), "--wheel", "--clear")
44
+ log_run(
44
45
  "uv",
45
46
  "publish",
46
47
  *([] if username is None else ["--username", username]),
@@ -2,19 +2,19 @@ from __future__ import annotations
2
2
 
3
3
  from typed_settings import Secret, load_settings, option, secret, settings
4
4
 
5
- from actions.utilities import LOADER, empty_str_to_none
5
+ from actions.utilities import LOADER, convert_secret_str, convert_str
6
6
 
7
7
 
8
8
  @settings
9
- class PublishSettings:
9
+ class Settings:
10
10
  username: str | None = option(
11
- default=None, converter=empty_str_to_none, help="The username of the upload"
11
+ default=None, converter=convert_str, help="The username of the upload"
12
12
  )
13
13
  password: Secret[str] | None = secret(
14
- default=None, converter=empty_str_to_none, help="The password for the upload"
14
+ default=None, converter=convert_secret_str, help="The password for the upload"
15
15
  )
16
16
  publish_url: str | None = option(
17
- default=None, converter=empty_str_to_none, help="The URL of the upload endpoint"
17
+ default=None, converter=convert_str, help="The URL of the upload endpoint"
18
18
  )
19
19
  trusted_publishing: bool = option(
20
20
  default=False, help="Configure trusted publishing"
@@ -25,7 +25,7 @@ class PublishSettings:
25
25
  )
26
26
 
27
27
 
28
- PUBLISH_SETTINGS = load_settings(PublishSettings, [LOADER])
28
+ SETTINGS = load_settings(Settings, [LOADER])
29
29
 
30
30
 
31
- __all__ = ["PUBLISH_SETTINGS", "PublishSettings"]
31
+ __all__ = ["SETTINGS", "Settings"]
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -8,27 +8,23 @@ from utilities.text import strip_and_dedent
8
8
 
9
9
  from actions import __version__
10
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
11
+ from actions.random_sleep.lib import random_sleep
12
+ from actions.random_sleep.settings import Settings
14
13
  from actions.utilities import LOADER
15
14
 
16
15
 
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:
16
+ @click_options(Settings, [LOADER], show_envvars_in_help=True, argname="sleep")
17
+ def random_sleep_sub_cmd(*, sleep: Settings) -> None:
20
18
  if is_pytest():
21
19
  return
22
20
  basic_config(obj=LOGGER)
23
21
  LOGGER.info(
24
22
  strip_and_dedent("""
25
- Running '%r' (version %s) with settings:
26
- %s
23
+ Running '%s' (version %s) with settings:
27
24
  %s
28
25
  """),
29
26
  random_sleep.__name__,
30
27
  __version__,
31
- pretty_repr(common),
32
28
  pretty_repr(sleep),
33
29
  )
34
30
  random_sleep(
@@ -36,4 +32,4 @@ def sleep_sub_cmd(*, common: CommonSettings, sleep: SleepSettings) -> None:
36
32
  )
37
33
 
38
34
 
39
- __all__ = ["sleep_sub_cmd"]
35
+ __all__ = ["random_sleep_sub_cmd"]
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ DOCSTRING = "Random sleep with logging"
4
+
5
+
6
+ __all__ = ["DOCSTRING"]
@@ -4,29 +4,30 @@ from math import ceil, floor
4
4
  from random import choice
5
5
  from time import sleep
6
6
 
7
+ from utilities.text import strip_and_dedent
7
8
  from utilities.whenever import get_now
8
9
  from whenever import TimeDelta, ZonedDateTime
9
10
 
10
11
  from actions import __version__
11
12
  from actions.logging import LOGGER
12
- from actions.sleep.settings import SLEEP_SETTINGS
13
+ from actions.random_sleep.settings import SETTINGS
13
14
 
14
15
 
15
16
  def random_sleep(
16
17
  *,
17
- min_: int = SLEEP_SETTINGS.min,
18
- max_: int = SLEEP_SETTINGS.max,
19
- step: int = SLEEP_SETTINGS.step,
20
- log_freq: int = SLEEP_SETTINGS.log_freq,
18
+ min_: int = SETTINGS.min,
19
+ max_: int = SETTINGS.max,
20
+ step: int = SETTINGS.step,
21
+ log_freq: int = SETTINGS.log_freq,
21
22
  ) -> None:
22
23
  LOGGER.info(
23
- """\
24
- Running %r (version %s) with settings:
25
- - min_ = %s
26
- - max_ = %s
27
- - step = %s
28
- - log_freq = %s
29
- """,
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
+ """),
30
31
  random_sleep.__name__,
31
32
  __version__,
32
33
  min_,
@@ -49,7 +50,7 @@ def _intermediate(
49
50
  end: ZonedDateTime,
50
51
  /,
51
52
  *,
52
- log_freq: int = SLEEP_SETTINGS.log_freq,
53
+ log_freq: int = SETTINGS.log_freq,
53
54
  ) -> None:
54
55
  elapsed = TimeDelta(seconds=floor((now - start).in_seconds()))
55
56
  remaining = TimeDelta(seconds=ceil((end - now).in_seconds()))
@@ -6,14 +6,14 @@ from actions.utilities import LOADER
6
6
 
7
7
 
8
8
  @settings
9
- class SleepSettings:
9
+ class Settings:
10
10
  min: int = option(default=0, help="Minimum duration, in seconds")
11
11
  max: int = option(default=3600, help="Maximum duration, in seconds")
12
12
  step: int = option(default=1, help="Step duration, in seconds")
13
13
  log_freq: int = option(default=60, help="Log frequency, in seconds")
14
14
 
15
15
 
16
- SLEEP_SETTINGS = load_settings(SleepSettings, [LOADER])
16
+ SETTINGS = load_settings(Settings, [LOADER])
17
17
 
18
18
 
19
- __all__ = ["SLEEP_SETTINGS", "SleepSettings"]
19
+ __all__ = ["SETTINGS", "Settings"]
@@ -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.replace_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"]