code-standards 7.0.0__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.
- code_standards-7.0.0.dist-info/METADATA +53 -0
- code_standards-7.0.0.dist-info/RECORD +99 -0
- code_standards-7.0.0.dist-info/WHEEL +4 -0
- code_standards-7.0.0.dist-info/entry_points.txt +3 -0
- code_standards-7.0.0.dist-info/licenses/LICENSE +21 -0
- sarj_standards/__init__.py +30 -0
- sarj_standards/__main__.py +5 -0
- sarj_standards/_meta.py +22 -0
- sarj_standards/api.py +890 -0
- sarj_standards/cli/__init__.py +0 -0
- sarj_standards/cli/main.py +2466 -0
- sarj_standards/configs/cli-reference.v1.json +1 -0
- sarj_standards/configs/doctor.config.json +22 -0
- sarj_standards/configs/eslint.application.mjs +1366 -0
- sarj_standards/configs/eslint.peers.json +44 -0
- sarj_standards/configs/eslint.strict.mjs +1060 -0
- sarj_standards/configs/markdownlint.strict.yaml +12 -0
- sarj_standards/configs/pyright.strict.json +96 -0
- sarj_standards/configs/ruff.application.toml +363 -0
- sarj_standards/configs/ruff.strict.toml +338 -0
- sarj_standards/configs/rule-inventory.v1.json +1 -0
- sarj_standards/configs/rule-ledger.json +846 -0
- sarj_standards/configs/rule-warning-levels.v1.json +1 -0
- sarj_standards/configs/taplo.strict.toml +14 -0
- sarj_standards/configs/yamllint.strict.yaml +25 -0
- sarj_standards/libs/__init__.py +0 -0
- sarj_standards/libs/adoption/__init__.py +0 -0
- sarj_standards/libs/adoption/configs.py +36 -0
- sarj_standards/libs/adoption/doctor.py +1346 -0
- sarj_standards/libs/adoption/exclusions.py +66 -0
- sarj_standards/libs/adoption/hooks.py +423 -0
- sarj_standards/libs/adoption/launcher.py +240 -0
- sarj_standards/libs/adoption/lifecycle.py +493 -0
- sarj_standards/libs/adoption/manifest.py +550 -0
- sarj_standards/libs/adoption/packagemanager.py +285 -0
- sarj_standards/libs/adoption/retired_suppressions.py +371 -0
- sarj_standards/libs/adoption/scaffold.py +1660 -0
- sarj_standards/libs/adoption/service.py +441 -0
- sarj_standards/libs/adoption/transaction.py +274 -0
- sarj_standards/libs/adoption/upgrade.py +516 -0
- sarj_standards/libs/adoption/uvtool.py +62 -0
- sarj_standards/libs/catalogs/__init__.py +9 -0
- sarj_standards/libs/catalogs/slack_automations.py +627 -0
- sarj_standards/libs/corpus/__init__.py +25 -0
- sarj_standards/libs/corpus/manifest.py +211 -0
- sarj_standards/libs/corpus/snapshot.py +222 -0
- sarj_standards/libs/diagnostics/__init__.py +65 -0
- sarj_standards/libs/diagnostics/analysis.schema.json +161 -0
- sarj_standards/libs/diagnostics/baseline.py +131 -0
- sarj_standards/libs/diagnostics/models.py +574 -0
- sarj_standards/libs/diagnostics/serialize.py +290 -0
- sarj_standards/libs/diagnostics/source.py +172 -0
- sarj_standards/libs/filesystem.py +11 -0
- sarj_standards/libs/linting/__init__.py +0 -0
- sarj_standards/libs/linting/analysis.py +422 -0
- sarj_standards/libs/linting/external.py +1454 -0
- sarj_standards/libs/linting/library_policy.py +688 -0
- sarj_standards/libs/linting/policy.py +152 -0
- sarj_standards/libs/linting/runner.py +442 -0
- sarj_standards/libs/linting/textlint.py +1605 -0
- sarj_standards/libs/release/__init__.py +98 -0
- sarj_standards/libs/release/_values.py +24 -0
- sarj_standards/libs/release/artifacts.py +191 -0
- sarj_standards/libs/release/causality.py +80 -0
- sarj_standards/libs/release/changes.py +48 -0
- sarj_standards/libs/release/process.py +128 -0
- sarj_standards/libs/release/publish.py +85 -0
- sarj_standards/libs/release/registry.py +271 -0
- sarj_standards/libs/release/release_age.py +218 -0
- sarj_standards/libs/release/rollout.py +1163 -0
- sarj_standards/libs/release/tags.py +373 -0
- sarj_standards/libs/release/typescript.py +191 -0
- sarj_standards/libs/repository/__init__.py +0 -0
- sarj_standards/libs/repository/cli_reference_artifact.py +324 -0
- sarj_standards/libs/repository/comment_corpus.py +536 -0
- sarj_standards/libs/repository/config_generation.py +146 -0
- sarj_standards/libs/repository/docs.py +347 -0
- sarj_standards/libs/repository/hooks.py +118 -0
- sarj_standards/libs/repository/ledger.py +99 -0
- sarj_standards/libs/repository/repository.py +744 -0
- sarj_standards/libs/repository/rule_authoring.py +246 -0
- sarj_standards/libs/repository/rule_catalog_artifact.py +479 -0
- sarj_standards/libs/repository/rule_changes.py +318 -0
- sarj_standards/libs/repository/rule_inventory_artifact.py +142 -0
- sarj_standards/libs/repository/rule_lifecycle.py +167 -0
- sarj_standards/libs/repository/rule_maintenance.py +225 -0
- sarj_standards/libs/rules/__init__.py +74 -0
- sarj_standards/libs/rules/catalog.py +145 -0
- sarj_standards/libs/rules/contracts.py +382 -0
- sarj_standards/libs/rules/corpus_runner.py +365 -0
- sarj_standards/libs/rules/evaluation.py +177 -0
- sarj_standards/libs/setup/__init__.py +4 -0
- sarj_standards/libs/setup/repository.py +40 -0
- sarj_standards/py.typed +0 -0
- sarj_standards/schemas/__init__.py +4 -0
- sarj_standards/schemas/_paths.py +7 -0
- sarj_standards/schemas/rule-catalog.v1.json +1 -0
- sarj_standards/schemas/rule-catalog.v1.schema.json +112 -0
- sarj_standards/schemas/slack-automations.v1.schema.json +1751 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# argparse exposes no public parser-graph traversal API.
|
|
2
|
+
# pyright: reportPrivateUsage=false
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Final, Literal, TypedDict, TypeGuard
|
|
11
|
+
|
|
12
|
+
from sarj_standards._meta import CONFIGS_DIR, __version__
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Iterable
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
SCHEMA_VERSION: Final = 1
|
|
20
|
+
CLI_REFERENCE_PATH: Final = CONFIGS_DIR / "cli-reference.v1.json"
|
|
21
|
+
_REPOSITORY_REFERENCE_PATH: Final = Path("packages/standards/src/sarj_standards/configs/cli-reference.v1.json")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True)
|
|
25
|
+
class ReferenceSyncResult:
|
|
26
|
+
status: int
|
|
27
|
+
message: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ReferenceArgument(TypedDict):
|
|
31
|
+
kind: Literal["positional", "option"]
|
|
32
|
+
names: list[str]
|
|
33
|
+
metavar: str | None
|
|
34
|
+
summary: str
|
|
35
|
+
choices: list[str]
|
|
36
|
+
required: bool
|
|
37
|
+
repeatable: bool
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ReferenceCommand(TypedDict):
|
|
41
|
+
name: str
|
|
42
|
+
path: list[str]
|
|
43
|
+
usage: str
|
|
44
|
+
summary: str
|
|
45
|
+
options: list[ReferenceArgument]
|
|
46
|
+
commands: list[ReferenceCommand]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ReferenceLauncher(TypedDict):
|
|
50
|
+
install: str
|
|
51
|
+
runLatest: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class CliReference(TypedDict):
|
|
55
|
+
schemaVersion: int
|
|
56
|
+
version: str
|
|
57
|
+
program: str
|
|
58
|
+
summary: str
|
|
59
|
+
epilog: str | None
|
|
60
|
+
globalOptions: list[ReferenceArgument]
|
|
61
|
+
commands: list[ReferenceCommand]
|
|
62
|
+
launcher: ReferenceLauncher
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def validate(value: object) -> CliReference:
|
|
66
|
+
if not _is_object(value) or frozenset(value) != frozenset(
|
|
67
|
+
{
|
|
68
|
+
"schemaVersion",
|
|
69
|
+
"version",
|
|
70
|
+
"program",
|
|
71
|
+
"summary",
|
|
72
|
+
"epilog",
|
|
73
|
+
"globalOptions",
|
|
74
|
+
"commands",
|
|
75
|
+
"launcher",
|
|
76
|
+
}
|
|
77
|
+
):
|
|
78
|
+
msg = "CLI reference has an invalid top-level shape"
|
|
79
|
+
raise ValueError(msg)
|
|
80
|
+
if value["schemaVersion"] != SCHEMA_VERSION:
|
|
81
|
+
msg = f"unsupported CLI reference schemaVersion: {value['schemaVersion']!r}; expected {SCHEMA_VERSION}"
|
|
82
|
+
raise ValueError(msg)
|
|
83
|
+
if not isinstance(value["version"], str) or not value["version"]:
|
|
84
|
+
msg = "CLI reference version must be a non-empty string"
|
|
85
|
+
raise ValueError(msg)
|
|
86
|
+
if not isinstance(value["program"], str) or not value["program"]:
|
|
87
|
+
msg = "CLI reference program must be a non-empty string"
|
|
88
|
+
raise ValueError(msg)
|
|
89
|
+
if not isinstance(value["summary"], str):
|
|
90
|
+
msg = "CLI reference summary must be a string"
|
|
91
|
+
raise TypeError(msg)
|
|
92
|
+
epilog = value["epilog"]
|
|
93
|
+
if epilog is not None and not isinstance(epilog, str):
|
|
94
|
+
msg = "CLI reference epilog must be null or a string"
|
|
95
|
+
raise TypeError(msg)
|
|
96
|
+
global_options = value["globalOptions"]
|
|
97
|
+
commands = value["commands"]
|
|
98
|
+
launcher = value["launcher"]
|
|
99
|
+
if not _is_argument_list(global_options) or not _is_command_list(commands) or not _is_launcher(launcher):
|
|
100
|
+
msg = "CLI reference options and commands must have the documented shape"
|
|
101
|
+
raise TypeError(msg)
|
|
102
|
+
return {
|
|
103
|
+
"schemaVersion": SCHEMA_VERSION,
|
|
104
|
+
"version": value["version"],
|
|
105
|
+
"program": value["program"],
|
|
106
|
+
"summary": value["summary"],
|
|
107
|
+
"epilog": epilog,
|
|
108
|
+
"globalOptions": global_options,
|
|
109
|
+
"commands": commands,
|
|
110
|
+
"launcher": launcher,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _is_object(value: object) -> TypeGuard[dict[str, object]]:
|
|
115
|
+
return isinstance(value, dict)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _is_object_list(value: object) -> TypeGuard[list[object]]:
|
|
119
|
+
return isinstance(value, list)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _is_argument_list(value: object) -> TypeGuard[list[ReferenceArgument]]:
|
|
123
|
+
return _is_object_list(value) and all(_is_argument(item) for item in value)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _is_argument(value: object) -> TypeGuard[ReferenceArgument]:
|
|
127
|
+
if not _is_object(value) or frozenset(value) != frozenset(ReferenceArgument.__required_keys__):
|
|
128
|
+
return False
|
|
129
|
+
kind = value["kind"]
|
|
130
|
+
metavar = value["metavar"]
|
|
131
|
+
return (
|
|
132
|
+
kind in {"positional", "option"}
|
|
133
|
+
and _is_string_list(value["names"])
|
|
134
|
+
and (metavar is None or isinstance(metavar, str))
|
|
135
|
+
and isinstance(value["summary"], str)
|
|
136
|
+
and _is_string_list(value["choices"])
|
|
137
|
+
and isinstance(value["required"], bool)
|
|
138
|
+
and isinstance(value["repeatable"], bool)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _is_string_list(value: object) -> TypeGuard[list[str]]:
|
|
143
|
+
return _is_object_list(value) and all(isinstance(item, str) for item in value)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _is_command_list(value: object) -> TypeGuard[list[ReferenceCommand]]:
|
|
147
|
+
return _is_object_list(value) and all(_is_command(item) for item in value)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _is_command(value: object) -> TypeGuard[ReferenceCommand]:
|
|
151
|
+
if not _is_object(value) or frozenset(value) != frozenset(ReferenceCommand.__required_keys__):
|
|
152
|
+
return False
|
|
153
|
+
return (
|
|
154
|
+
isinstance(value["name"], str)
|
|
155
|
+
and _is_string_list(value["path"])
|
|
156
|
+
and isinstance(value["usage"], str)
|
|
157
|
+
and isinstance(value["summary"], str)
|
|
158
|
+
and _is_argument_list(value["options"])
|
|
159
|
+
and _is_command_list(value["commands"])
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _is_launcher(value: object) -> TypeGuard[ReferenceLauncher]:
|
|
164
|
+
return (
|
|
165
|
+
_is_object(value)
|
|
166
|
+
and frozenset(value) == frozenset(ReferenceLauncher.__required_keys__)
|
|
167
|
+
and isinstance(value["install"], str)
|
|
168
|
+
and bool(value["install"])
|
|
169
|
+
and isinstance(value["runLatest"], str)
|
|
170
|
+
and bool(value["runLatest"])
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def load(path: Path = CLI_REFERENCE_PATH) -> CliReference:
|
|
175
|
+
try:
|
|
176
|
+
payload: object = json.loads(path.read_text(encoding="utf-8")) # pyright: ignore[reportAny]
|
|
177
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
178
|
+
msg = f"cannot load shipped CLI reference {path}: {exc}"
|
|
179
|
+
raise ValueError(msg) from exc
|
|
180
|
+
return validate(payload)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def build(parser: argparse.ArgumentParser) -> CliReference:
|
|
184
|
+
from sarj_standards.libs.adoption import launcher # ruff: ignore[import-outside-top-level]
|
|
185
|
+
|
|
186
|
+
root = _command(
|
|
187
|
+
parser,
|
|
188
|
+
name=parser.prog,
|
|
189
|
+
path=(),
|
|
190
|
+
summary=parser.description or "",
|
|
191
|
+
)
|
|
192
|
+
return validate(
|
|
193
|
+
{
|
|
194
|
+
"schemaVersion": SCHEMA_VERSION,
|
|
195
|
+
"version": __version__,
|
|
196
|
+
"program": parser.prog,
|
|
197
|
+
"summary": parser.description or "",
|
|
198
|
+
"epilog": parser.epilog,
|
|
199
|
+
"globalOptions": root["options"],
|
|
200
|
+
"commands": root["commands"],
|
|
201
|
+
"launcher": {"install": launcher.install(), "runLatest": launcher.latest()},
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _command(
|
|
207
|
+
parser: argparse.ArgumentParser,
|
|
208
|
+
*,
|
|
209
|
+
name: str,
|
|
210
|
+
path: tuple[str, ...],
|
|
211
|
+
summary: str,
|
|
212
|
+
) -> ReferenceCommand:
|
|
213
|
+
arguments: list[ReferenceArgument] = []
|
|
214
|
+
commands: list[ReferenceCommand] = []
|
|
215
|
+
for action in parser._actions: # ruff: ignore[private-member-access]
|
|
216
|
+
if _is_subparsers(action):
|
|
217
|
+
summaries = _subcommand_summaries(action)
|
|
218
|
+
for command_name, command_parser in action.choices.items():
|
|
219
|
+
commands.append(
|
|
220
|
+
_command(
|
|
221
|
+
command_parser,
|
|
222
|
+
name=command_name,
|
|
223
|
+
path=(*path, command_name),
|
|
224
|
+
summary=summaries.get(command_name, ""),
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
continue
|
|
228
|
+
arguments.append(_argument(action))
|
|
229
|
+
return {
|
|
230
|
+
"name": name,
|
|
231
|
+
"path": list(path),
|
|
232
|
+
"usage": _usage(parser.prog, arguments, commands),
|
|
233
|
+
"summary": summary,
|
|
234
|
+
"options": arguments,
|
|
235
|
+
"commands": commands,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _is_subparsers(
|
|
240
|
+
action: argparse.Action,
|
|
241
|
+
) -> TypeGuard[argparse._SubParsersAction[argparse.ArgumentParser]]:
|
|
242
|
+
return isinstance(action, argparse._SubParsersAction) # ruff: ignore[private-member-access]
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _subcommand_summaries(
|
|
246
|
+
action: argparse._SubParsersAction[argparse.ArgumentParser],
|
|
247
|
+
) -> dict[str, str]:
|
|
248
|
+
return {
|
|
249
|
+
choice.dest: "" if choice.help in {None, argparse.SUPPRESS} else str(choice.help)
|
|
250
|
+
for choice in action._choices_actions # ruff: ignore[private-member-access]
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _argument(action: argparse.Action) -> ReferenceArgument:
|
|
255
|
+
choices: Iterable[object] | None = action.choices
|
|
256
|
+
positional = not action.option_strings
|
|
257
|
+
return {
|
|
258
|
+
"kind": "positional" if positional else "option",
|
|
259
|
+
"names": [action.dest] if positional else list(action.option_strings),
|
|
260
|
+
"metavar": _metavar(action),
|
|
261
|
+
"summary": "" if action.help in {None, argparse.SUPPRESS} else str(action.help),
|
|
262
|
+
"choices": [] if choices is None else [str(choice) for choice in choices],
|
|
263
|
+
"required": action.required,
|
|
264
|
+
"repeatable": action.nargs in {"+", "*"}
|
|
265
|
+
or type(action).__name__ in {"_AppendAction", "_AppendConstAction", "_CountAction"},
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _metavar(action: argparse.Action) -> str | None:
|
|
270
|
+
if action.nargs == 0:
|
|
271
|
+
return None
|
|
272
|
+
if isinstance(action.metavar, tuple):
|
|
273
|
+
return " ".join(str(item) for item in action.metavar)
|
|
274
|
+
if action.metavar is not None:
|
|
275
|
+
return str(action.metavar)
|
|
276
|
+
return action.dest if not action.option_strings else action.dest.upper()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _usage(
|
|
280
|
+
program: str,
|
|
281
|
+
arguments: list[ReferenceArgument],
|
|
282
|
+
commands: list[ReferenceCommand],
|
|
283
|
+
) -> str:
|
|
284
|
+
parts = [program]
|
|
285
|
+
for argument in arguments:
|
|
286
|
+
if not argument["names"]:
|
|
287
|
+
continue
|
|
288
|
+
token = argument["names"][0]
|
|
289
|
+
metavar = argument["metavar"]
|
|
290
|
+
if metavar is not None:
|
|
291
|
+
token = f"{token} {metavar}" if argument["kind"] == "option" else metavar
|
|
292
|
+
if argument["repeatable"]:
|
|
293
|
+
token = f"{token} ..."
|
|
294
|
+
if not argument["required"]:
|
|
295
|
+
token = f"[{token}]"
|
|
296
|
+
parts.append(token)
|
|
297
|
+
if commands:
|
|
298
|
+
parts.append("{" + ",".join(command["name"] for command in commands) + "}")
|
|
299
|
+
return " ".join(parts)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def render(parser: argparse.ArgumentParser) -> str:
|
|
303
|
+
return json.dumps(build(parser), ensure_ascii=False, separators=(",", ":"), sort_keys=True) + "\n"
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def sync(root: Path, parser: argparse.ArgumentParser, *, check: bool) -> ReferenceSyncResult:
|
|
307
|
+
from sarj_standards.libs.adoption import transaction # ruff: ignore[import-outside-top-level]
|
|
308
|
+
|
|
309
|
+
destination = root.resolve() / _REPOSITORY_REFERENCE_PATH
|
|
310
|
+
expected = render(parser)
|
|
311
|
+
try:
|
|
312
|
+
current = destination.read_text(encoding="utf-8")
|
|
313
|
+
except FileNotFoundError:
|
|
314
|
+
current = ""
|
|
315
|
+
if current == expected:
|
|
316
|
+
return ReferenceSyncResult(0, "ok: cli-reference.v1.json matches the parser graph")
|
|
317
|
+
if check:
|
|
318
|
+
return ReferenceSyncResult(
|
|
319
|
+
1,
|
|
320
|
+
"drift: cli-reference.v1.json differs from the parser graph; "
|
|
321
|
+
"run `code-standards maintain cli-reference sync`",
|
|
322
|
+
)
|
|
323
|
+
transaction.atomic_write_text(root.resolve(), destination, expected)
|
|
324
|
+
return ReferenceSyncResult(0, "updated: cli-reference.v1.json")
|