apexcodexpy 0.7.3__tar.gz → 0.7.4__tar.gz
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.
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/PKG-INFO +1 -1
- apexcodexpy-0.7.4/codex/infra/pyproject.py +190 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/result.py +3 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/service.py +18 -17
- apexcodexpy-0.7.4/codex/infra/typer/device.py +52 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/typer/root.py +0 -15
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/workflows/base.py +1 -6
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/pyproject.toml +1 -2
- apexcodexpy-0.7.3/codex/infra/pyproject.py +0 -157
- apexcodexpy-0.7.3/codex/infra/typer/device.py +0 -28
- apexcodexpy-0.7.3/codex/infra/workflows/lock.py +0 -34
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/LICENSE +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/README.md +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/__init__.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/__init__.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/program.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/reporters.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/tools.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/typer/__init__.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/typer/testing.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/workflows/__init__.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/workflows/build.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/workflows/ci.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/workflows/dependabot.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/workflows/validate.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/infra/yamler.py +0 -0
- {apexcodexpy-0.7.3 → apexcodexpy-0.7.4}/codex/runner.py +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import hashlib
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
import tomlkit
|
|
11
|
+
|
|
12
|
+
from codex.infra.result import ProgramResult
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Mapping
|
|
16
|
+
|
|
17
|
+
from codex.infra.program import Executable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class PyProjectToml:
|
|
22
|
+
name: str = "PyProject"
|
|
23
|
+
path: Path = Path("pyproject.toml")
|
|
24
|
+
|
|
25
|
+
def sync(self, target: Path) -> Executable:
|
|
26
|
+
pyproject = PyProjectFile(target / self.path, standards=self.standards)
|
|
27
|
+
|
|
28
|
+
if not pyproject.exists():
|
|
29
|
+
return ProgramResult(name=self.name).fail(message=f"{self.name} not found.")
|
|
30
|
+
|
|
31
|
+
if not pyproject.is_outdated():
|
|
32
|
+
return ProgramResult(name=self.name).no_changes()
|
|
33
|
+
|
|
34
|
+
pyproject.sync()
|
|
35
|
+
|
|
36
|
+
return ProgramResult(name=self.name).workflow_success()
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def standards(self) -> Mapping[str, Any]:
|
|
40
|
+
return {
|
|
41
|
+
"tool": {
|
|
42
|
+
"mypy": self._mypy,
|
|
43
|
+
"ruff": self._ruff,
|
|
44
|
+
"coverage": self._coveragepy,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def _mypy(self) -> dict[str, Any]:
|
|
50
|
+
return {
|
|
51
|
+
"strict": True,
|
|
52
|
+
"ignore_missing_imports": True,
|
|
53
|
+
"show_error_codes": True,
|
|
54
|
+
"pretty": True,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def _ruff(self) -> dict[str, Any]:
|
|
59
|
+
return {
|
|
60
|
+
"lint": {
|
|
61
|
+
"select": [
|
|
62
|
+
"A",
|
|
63
|
+
"ARG",
|
|
64
|
+
"B",
|
|
65
|
+
"C4",
|
|
66
|
+
"PT",
|
|
67
|
+
"RSE",
|
|
68
|
+
"RET",
|
|
69
|
+
"SIM",
|
|
70
|
+
"T20",
|
|
71
|
+
"E",
|
|
72
|
+
"W",
|
|
73
|
+
"F",
|
|
74
|
+
"I",
|
|
75
|
+
"N",
|
|
76
|
+
"UP",
|
|
77
|
+
"RUF",
|
|
78
|
+
"FURB",
|
|
79
|
+
"PERF",
|
|
80
|
+
"LOG",
|
|
81
|
+
"PIE",
|
|
82
|
+
"SLF",
|
|
83
|
+
"DTZ",
|
|
84
|
+
"PTH",
|
|
85
|
+
"TRY",
|
|
86
|
+
"PL",
|
|
87
|
+
"TCH",
|
|
88
|
+
],
|
|
89
|
+
"mccabe": {
|
|
90
|
+
"max-complexity": 10,
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def _coveragepy(self) -> dict[str, Any]:
|
|
97
|
+
return {
|
|
98
|
+
"run": {
|
|
99
|
+
"branch": True,
|
|
100
|
+
},
|
|
101
|
+
"report": {
|
|
102
|
+
"skip_empty": True,
|
|
103
|
+
"skip_covered": True,
|
|
104
|
+
"show_missing": True,
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass(frozen=True)
|
|
110
|
+
class PyProjectFile:
|
|
111
|
+
path: Path
|
|
112
|
+
|
|
113
|
+
standards: Mapping[str, Any] = field(default_factory=dict)
|
|
114
|
+
|
|
115
|
+
@cached_property
|
|
116
|
+
def current(self) -> Tomler:
|
|
117
|
+
return Tomler.read(self.path)
|
|
118
|
+
|
|
119
|
+
@cached_property
|
|
120
|
+
def latest(self) -> Tomler:
|
|
121
|
+
return self.current.update_with(self.standards)
|
|
122
|
+
|
|
123
|
+
def exists(self) -> bool:
|
|
124
|
+
return self.path.exists()
|
|
125
|
+
|
|
126
|
+
def is_outdated(self) -> bool:
|
|
127
|
+
return self.current != self.latest
|
|
128
|
+
|
|
129
|
+
def sync(self) -> None:
|
|
130
|
+
self.path.write_text(self.latest.dumps(), encoding="utf-8", newline="\n")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass(frozen=True)
|
|
134
|
+
class Tomler:
|
|
135
|
+
document: Mapping[str, Any]
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def read(cls, path: Path) -> Tomler:
|
|
139
|
+
return cls(tomlkit.parse(path.read_text(encoding="utf-8")))
|
|
140
|
+
|
|
141
|
+
def dumps(self) -> str:
|
|
142
|
+
return tomlkit.dumps(self.document)
|
|
143
|
+
|
|
144
|
+
def update_with(self, source: Mapping[str, Any]) -> Tomler:
|
|
145
|
+
return Tomler(deep_merge(source, self.document))
|
|
146
|
+
|
|
147
|
+
def hashed(self) -> str:
|
|
148
|
+
return hashlib.sha256(self.dumps().encode()).hexdigest()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def deep_merge(
|
|
152
|
+
source: Mapping[str, Any],
|
|
153
|
+
destination: Mapping[str, Any],
|
|
154
|
+
) -> Mapping[str, Any]:
|
|
155
|
+
"""
|
|
156
|
+
Recursively merges two mappings, taking the source and merging it into
|
|
157
|
+
the destination. Performs a deep merge such that nested dictionaries or
|
|
158
|
+
arrays are combined intelligently.
|
|
159
|
+
|
|
160
|
+
The function identifies the type of each key-value pair in the `source`
|
|
161
|
+
and `destination` mappings and determines how to merge them based on
|
|
162
|
+
their data types, supporting nested dictionaries and TOML data
|
|
163
|
+
structures.
|
|
164
|
+
|
|
165
|
+
:param source: The source mapping to merge into the destination.
|
|
166
|
+
:param destination: The destination mapping that will be updated with the
|
|
167
|
+
data from `source`.
|
|
168
|
+
:return: A new mapping that represents the merged result of `source`
|
|
169
|
+
and `destination`.
|
|
170
|
+
"""
|
|
171
|
+
result = {**destination}
|
|
172
|
+
|
|
173
|
+
for key, value in source.items():
|
|
174
|
+
match (result.get(key), value):
|
|
175
|
+
case (dict(destination_value), dict(source_value)):
|
|
176
|
+
# Both exist and are dicts -> recursively merge
|
|
177
|
+
result[key] = deep_merge(source_value, destination_value)
|
|
178
|
+
case (_, dict(source_value)):
|
|
179
|
+
# Only source is a dict -> convert to TOML table
|
|
180
|
+
result[key] = deep_merge(source_value, {})
|
|
181
|
+
case (_, list(src_list)):
|
|
182
|
+
# Formatted TOML array
|
|
183
|
+
new_array = tomlkit.array()
|
|
184
|
+
new_array.extend(copy.deepcopy(src_list))
|
|
185
|
+
new_array.multiline(True)
|
|
186
|
+
result[key] = new_array
|
|
187
|
+
case _:
|
|
188
|
+
result[key] = copy.deepcopy(value)
|
|
189
|
+
|
|
190
|
+
return result
|
|
@@ -61,6 +61,9 @@ class ProgramResult:
|
|
|
61
61
|
def no_changes(self) -> ProgramResult:
|
|
62
62
|
return self.success(message="Already up to date.\n")
|
|
63
63
|
|
|
64
|
+
def workflow_success(self) -> ProgramResult:
|
|
65
|
+
return self.success(message="Workflow succeeded!\n")
|
|
66
|
+
|
|
64
67
|
def success(self, *, message: str = "") -> ProgramResult:
|
|
65
68
|
return ProgramResult(
|
|
66
69
|
name=self.name,
|
|
@@ -22,7 +22,6 @@ from codex.infra.workflows import (
|
|
|
22
22
|
CheckCodeWorkflow,
|
|
23
23
|
DependabotWorkflow,
|
|
24
24
|
)
|
|
25
|
-
from codex.infra.workflows.lock import CodexLockWorkflow
|
|
26
25
|
|
|
27
26
|
if TYPE_CHECKING:
|
|
28
27
|
from pathlib import Path
|
|
@@ -137,14 +136,12 @@ class CodexTools:
|
|
|
137
136
|
def sync(self, force: bool) -> Executable:
|
|
138
137
|
return (
|
|
139
138
|
MultiProgram()
|
|
140
|
-
.attach(PyProjectToml().sync(self.target))
|
|
141
139
|
.attach(DependabotWorkflow().sync(self.target, force=force))
|
|
142
140
|
.attach(CheckCodeWorkflow().sync(self.target, force=force))
|
|
143
141
|
.attach(
|
|
144
142
|
BuildCodeWorkflow(disclaimer=False).sync(self.target, force=force),
|
|
145
143
|
when=self.target.joinpath("Dockerfile").exists(),
|
|
146
144
|
)
|
|
147
|
-
.attach(CodexLockWorkflow().sync(self.target, force=force))
|
|
148
145
|
)
|
|
149
146
|
|
|
150
147
|
def lock(self) -> Executable:
|
|
@@ -207,23 +204,27 @@ class CodexTools:
|
|
|
207
204
|
|
|
208
205
|
def fix(self, unsafe: bool = False) -> Executable:
|
|
209
206
|
return (
|
|
210
|
-
|
|
207
|
+
MultiProgram()
|
|
208
|
+
.attach(PyProjectToml().sync(self.target))
|
|
211
209
|
.attach(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
210
|
+
PoetryRun()
|
|
211
|
+
.attach(
|
|
212
|
+
Program(
|
|
213
|
+
name="black",
|
|
214
|
+
command=Ruff(on=self.target).format(),
|
|
215
|
+
)
|
|
215
216
|
)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
217
|
+
.attach(
|
|
218
|
+
Program(
|
|
219
|
+
name="ruff",
|
|
220
|
+
command=(
|
|
221
|
+
Ruff(on=self.target)
|
|
222
|
+
.lint()
|
|
223
|
+
.flag(fix=True)
|
|
224
|
+
.flag(unsafe_fixes=unsafe)
|
|
225
|
+
),
|
|
225
226
|
),
|
|
226
|
-
)
|
|
227
|
+
)
|
|
227
228
|
)
|
|
228
229
|
)
|
|
229
230
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from textwrap import dedent
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class TyperDevice:
|
|
15
|
+
color: str = typer.colors.RESET
|
|
16
|
+
|
|
17
|
+
def with_green(self) -> TyperDevice:
|
|
18
|
+
return TyperDevice(typer.colors.GREEN)
|
|
19
|
+
|
|
20
|
+
def with_red(self) -> TyperDevice:
|
|
21
|
+
return TyperDevice(typer.colors.RED)
|
|
22
|
+
|
|
23
|
+
def with_white(self) -> TyperDevice:
|
|
24
|
+
return TyperDevice(typer.colors.WHITE)
|
|
25
|
+
|
|
26
|
+
def with_yellow(self) -> TyperDevice:
|
|
27
|
+
return TyperDevice(typer.colors.YELLOW)
|
|
28
|
+
|
|
29
|
+
def echo(self, message: str) -> TyperDevice:
|
|
30
|
+
_Message.parse(message).display(using=lambda m: typer.secho(m, fg=self.color))
|
|
31
|
+
|
|
32
|
+
return self
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class _Message:
|
|
37
|
+
raw: str
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def parse(raw: str) -> _Message | _NoMessage:
|
|
41
|
+
message = dedent(raw)
|
|
42
|
+
|
|
43
|
+
return _Message(message) if message.strip() else _NoMessage()
|
|
44
|
+
|
|
45
|
+
def display(self, using: Callable[[str], None]) -> None:
|
|
46
|
+
using(self.raw)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class _NoMessage:
|
|
51
|
+
def display(self, using: Callable[[str], None]) -> None:
|
|
52
|
+
_ = using
|
|
@@ -4,25 +4,10 @@ import typer
|
|
|
4
4
|
|
|
5
5
|
from codex.infra.reporters import DefaultReporter, SimpleReporter
|
|
6
6
|
from codex.infra.service import Codex, Git
|
|
7
|
-
from codex.infra.workflows.lock import CodexLockWorkflow
|
|
8
7
|
|
|
9
8
|
cli = typer.Typer(no_args_is_help=True)
|
|
10
9
|
|
|
11
10
|
|
|
12
|
-
@cli.callback()
|
|
13
|
-
def verify_codex_lock(ctx: typer.Context) -> None:
|
|
14
|
-
if ctx.invoked_subcommand == "sync":
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
if CodexLockWorkflow().verify():
|
|
18
|
-
return
|
|
19
|
-
|
|
20
|
-
typer.echo("Codex is out of sync!")
|
|
21
|
-
typer.echo("Run `codex sync` to bring configuration up to date")
|
|
22
|
-
|
|
23
|
-
raise typer.Exit(1)
|
|
24
|
-
|
|
25
|
-
|
|
26
11
|
@cli.command(
|
|
27
12
|
name="version",
|
|
28
13
|
help="Show the codex version and exit.",
|
|
@@ -46,14 +46,9 @@ class YamlWorkflow(ABC):
|
|
|
46
46
|
|
|
47
47
|
standards.dump_to(path)
|
|
48
48
|
|
|
49
|
-
return self.
|
|
49
|
+
return ProgramResult(name=self.name).workflow_success()
|
|
50
50
|
|
|
51
51
|
return LazyExecutable(_)
|
|
52
52
|
|
|
53
53
|
def _utcnow(self) -> str:
|
|
54
54
|
return datetime.now(tz=UTC).replace(microsecond=0).isoformat(sep=" ")
|
|
55
|
-
|
|
56
|
-
def success(self) -> ProgramResult:
|
|
57
|
-
return ProgramResult(name=self.name).success(
|
|
58
|
-
message=f"{self.name} workflow succeeded.\n"
|
|
59
|
-
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "apexcodexpy"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.4"
|
|
4
4
|
description = "The Non-Destructive Configuration Steward for Python Projects."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -66,7 +66,6 @@ select = [
|
|
|
66
66
|
"PL",
|
|
67
67
|
"TCH",
|
|
68
68
|
]
|
|
69
|
-
|
|
70
69
|
ignore = [
|
|
71
70
|
"TRY003",
|
|
72
71
|
"PLR0913",
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import hashlib
|
|
4
|
-
from dataclasses import dataclass, field
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Self
|
|
7
|
-
|
|
8
|
-
import tomlkit
|
|
9
|
-
from tomlkit import TOMLDocument
|
|
10
|
-
|
|
11
|
-
from codex.infra.result import ProgramResult
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from collections.abc import Mapping, MutableMapping
|
|
15
|
-
|
|
16
|
-
from codex.infra.program import Executable
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _mypy_config() -> dict[str, Any]:
|
|
20
|
-
return {
|
|
21
|
-
"strict": True,
|
|
22
|
-
"ignore_missing_imports": True,
|
|
23
|
-
"show_error_codes": True,
|
|
24
|
-
"pretty": True,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _ruff_config() -> dict[str, Any]:
|
|
29
|
-
return {
|
|
30
|
-
"lint": {
|
|
31
|
-
"select": [
|
|
32
|
-
"A",
|
|
33
|
-
"ARG",
|
|
34
|
-
"B",
|
|
35
|
-
"C4",
|
|
36
|
-
"PT",
|
|
37
|
-
"RSE",
|
|
38
|
-
"RET",
|
|
39
|
-
"SIM",
|
|
40
|
-
"T20",
|
|
41
|
-
"E",
|
|
42
|
-
"W",
|
|
43
|
-
"F",
|
|
44
|
-
"I",
|
|
45
|
-
"N",
|
|
46
|
-
"UP",
|
|
47
|
-
"RUF",
|
|
48
|
-
"FURB",
|
|
49
|
-
"PERF",
|
|
50
|
-
"LOG",
|
|
51
|
-
"PIE",
|
|
52
|
-
"SLF",
|
|
53
|
-
"DTZ",
|
|
54
|
-
"PTH",
|
|
55
|
-
"TRY",
|
|
56
|
-
"PL",
|
|
57
|
-
"TCH",
|
|
58
|
-
],
|
|
59
|
-
"mccabe": {
|
|
60
|
-
"max-complexity": 10,
|
|
61
|
-
},
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _coverage_config() -> dict[str, Any]:
|
|
67
|
-
return {
|
|
68
|
-
"run": {
|
|
69
|
-
"branch": True,
|
|
70
|
-
},
|
|
71
|
-
"report": {
|
|
72
|
-
"skip_empty": True,
|
|
73
|
-
"skip_covered": True,
|
|
74
|
-
"show_missing": True,
|
|
75
|
-
},
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
STANDARDS = {
|
|
80
|
-
"tool": {
|
|
81
|
-
"mypy": _mypy_config(),
|
|
82
|
-
"ruff": _ruff_config(),
|
|
83
|
-
"coverage": _coverage_config(),
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@dataclass(frozen=True)
|
|
89
|
-
class PyProjectToml:
|
|
90
|
-
_result: ProgramResult = field(init=False, default=ProgramResult(name="PyProject"))
|
|
91
|
-
|
|
92
|
-
NAME = "pyproject.toml"
|
|
93
|
-
|
|
94
|
-
def sync(self, target: Path) -> Executable:
|
|
95
|
-
return self._sync(self.toml_on(target))
|
|
96
|
-
|
|
97
|
-
def _sync(self, pyproject: Path) -> Executable:
|
|
98
|
-
if not pyproject.exists():
|
|
99
|
-
return self._result.fail(message=f"{self.NAME} not found.")
|
|
100
|
-
|
|
101
|
-
document = Tomler.read(pyproject)
|
|
102
|
-
original = document.dumps()
|
|
103
|
-
modified = document.update_with(STANDARDS).dumps()
|
|
104
|
-
|
|
105
|
-
if original == modified:
|
|
106
|
-
return self._result.no_changes()
|
|
107
|
-
|
|
108
|
-
pyproject.write_text(modified, encoding="utf-8", newline="\n")
|
|
109
|
-
|
|
110
|
-
return self._result.success(message=f"{self.NAME} synchronized successfully.\n")
|
|
111
|
-
|
|
112
|
-
def toml_on(self, path: Path) -> Path:
|
|
113
|
-
return path / self.NAME
|
|
114
|
-
|
|
115
|
-
@staticmethod
|
|
116
|
-
def latest() -> Tomler:
|
|
117
|
-
return Tomler.read(Path("pyproject.toml")).update_with(STANDARDS)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
@dataclass(frozen=True)
|
|
121
|
-
class Tomler:
|
|
122
|
-
document: TOMLDocument
|
|
123
|
-
|
|
124
|
-
@classmethod
|
|
125
|
-
def read(cls, path: Path) -> Tomler:
|
|
126
|
-
return cls(tomlkit.parse(path.read_text(encoding="utf-8")))
|
|
127
|
-
|
|
128
|
-
def dumps(self) -> str:
|
|
129
|
-
return tomlkit.dumps(self.document)
|
|
130
|
-
|
|
131
|
-
def update_with(self, source: Mapping[str, Any]) -> Self:
|
|
132
|
-
deep_merge(source, self.document)
|
|
133
|
-
|
|
134
|
-
return self
|
|
135
|
-
|
|
136
|
-
def hashed(self) -> str:
|
|
137
|
-
return hashlib.sha256(self.dumps().encode()).hexdigest()
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def deep_merge(
|
|
141
|
-
source: Mapping[str, Any],
|
|
142
|
-
destination: MutableMapping[str, Any],
|
|
143
|
-
) -> None:
|
|
144
|
-
"""Recursively merges the source into destination with TOML formatting."""
|
|
145
|
-
for key, value in source.items():
|
|
146
|
-
match value:
|
|
147
|
-
case dict():
|
|
148
|
-
if key not in destination:
|
|
149
|
-
destination[key] = tomlkit.table()
|
|
150
|
-
deep_merge(value, destination[key])
|
|
151
|
-
case list():
|
|
152
|
-
new_array = tomlkit.array()
|
|
153
|
-
new_array.extend(value)
|
|
154
|
-
new_array.multiline(True)
|
|
155
|
-
destination[key] = new_array
|
|
156
|
-
case _:
|
|
157
|
-
destination[key] = value
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
import typer
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@dataclass(frozen=True)
|
|
9
|
-
class TyperDevice:
|
|
10
|
-
color: str = typer.colors.RESET
|
|
11
|
-
|
|
12
|
-
def with_green(self) -> TyperDevice:
|
|
13
|
-
return TyperDevice(typer.colors.GREEN)
|
|
14
|
-
|
|
15
|
-
def with_red(self) -> TyperDevice:
|
|
16
|
-
return TyperDevice(typer.colors.RED)
|
|
17
|
-
|
|
18
|
-
def with_white(self) -> TyperDevice:
|
|
19
|
-
return TyperDevice(typer.colors.WHITE)
|
|
20
|
-
|
|
21
|
-
def with_yellow(self) -> TyperDevice:
|
|
22
|
-
return TyperDevice(typer.colors.YELLOW)
|
|
23
|
-
|
|
24
|
-
def echo(self, message: str) -> TyperDevice:
|
|
25
|
-
if message.strip():
|
|
26
|
-
typer.secho(message, fg=self.color)
|
|
27
|
-
|
|
28
|
-
return self
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import hashlib
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from codex.infra.yamler import Yamler
|
|
8
|
-
|
|
9
|
-
from ..pyproject import PyProjectToml
|
|
10
|
-
from .base import YamlWorkflow
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def hash_of(path: Path) -> str:
|
|
14
|
-
with path.open("rb") as f:
|
|
15
|
-
return hashlib.file_digest(f, hashlib.sha256).hexdigest()
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@dataclass(frozen=True, kw_only=True)
|
|
19
|
-
class CodexLockWorkflow(YamlWorkflow):
|
|
20
|
-
name: str = "CodexLock"
|
|
21
|
-
path: Path = Path("codex.lock")
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def standards(self) -> Yamler:
|
|
25
|
-
return Yamler.parse(
|
|
26
|
-
{
|
|
27
|
-
"hashes": {
|
|
28
|
-
"pyproject": PyProjectToml.latest().hashed(),
|
|
29
|
-
},
|
|
30
|
-
}
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
def verify(self) -> bool:
|
|
34
|
-
return not self.path.exists() or self.standards == Yamler.read(self.path)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|