tac-validate 0.1.0__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.
- tac_validate-0.1.0/.gitignore +188 -0
- tac_validate-0.1.0/PKG-INFO +47 -0
- tac_validate-0.1.0/README.md +38 -0
- tac_validate-0.1.0/pyproject.toml +42 -0
- tac_validate-0.1.0/src/tac_validate/__init__.py +11 -0
- tac_validate-0.1.0/src/tac_validate/api.py +35 -0
- tac_validate-0.1.0/src/tac_validate/cli.py +79 -0
- tac_validate-0.1.0/src/tac_validate/codec.py +12 -0
- tac_validate-0.1.0/src/tac_validate/models.py +77 -0
- tac_validate-0.1.0/src/tac_validate/product_rules.py +364 -0
- tac_validate-0.1.0/src/tac_validate/products.py +18 -0
- tac_validate-0.1.0/src/tac_validate/rules.py +120 -0
- tac_validate-0.1.0/tests/fixtures/README.md +26 -0
- tac_validate-0.1.0/tests/fixtures/accept/airmet_basic.tac +2 -0
- tac_validate-0.1.0/tests/fixtures/accept/metar_basic.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/accept/sigmet_basic.tac +2 -0
- tac_validate-0.1.0/tests/fixtures/accept/speci_basic.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/accept/taf_basic.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/accept/tca_basic.tac +15 -0
- tac_validate-0.1.0/tests/fixtures/accept/vaa_basic.tac +15 -0
- tac_validate-0.1.0/tests/fixtures/manifest.json +284 -0
- tac_validate-0.1.0/tests/fixtures/negative/airmet/missing_valid.tac +2 -0
- tac_validate-0.1.0/tests/fixtures/negative/airmet/multi_phenomenon.tac +2 -0
- tac_validate-0.1.0/tests/fixtures/negative/metar/bad_cloud_token.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/metar/missing_cccc.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/metar/missing_obs_time.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/metar/missing_qnh.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/metar/missing_temp_dewpoint.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/metar/missing_visibility.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/metar/missing_wind.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/sigmet/missing_valid.tac +2 -0
- tac_validate-0.1.0/tests/fixtures/negative/sigmet/multi_phenomenon.tac +2 -0
- tac_validate-0.1.0/tests/fixtures/negative/speci/missing_cccc.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/speci/missing_temp_and_qnh.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/speci/missing_visibility.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/speci/missing_wind.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/taf/bad_cnl_shape.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/taf/missing_cccc.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/taf/missing_issue_time.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/taf/missing_validity.tac +1 -0
- tac_validate-0.1.0/tests/fixtures/negative/tca/missing_dtg.tac +11 -0
- tac_validate-0.1.0/tests/fixtures/negative/tca/missing_max_wind.tac +11 -0
- tac_validate-0.1.0/tests/fixtures/negative/vaa/missing_dtg.tac +9 -0
- tac_validate-0.1.0/tests/fixtures/negative/vaa/missing_vaac.tac +9 -0
- tac_validate-0.1.0/tests/test_issue_spans.py +30 -0
- tac_validate-0.1.0/tests/test_rule_offsets.py +61 -0
- tac_validate-0.1.0/tests/test_tac_issue_spans.py +30 -0
- tac_validate-0.1.0/tests/test_tac_validate_codec.py +12 -0
- tac_validate-0.1.0/tests/test_tac_validate_import.py +9 -0
- tac_validate-0.1.0/tests/test_tc_f10_002_terminator_info.py +53 -0
- tac_validate-0.1.0/tests/test_tc_f12_001_negative_fixtures.py +114 -0
- tac_validate-0.1.0/tests/test_tc_f12_001_template_gates.py +65 -0
- tac_validate-0.1.0/tests/test_tc_f12_cli_smoke.py +57 -0
- tac_validate-0.1.0/tests/test_tc_f12_wheel_smoke.py +96 -0
- tac_validate-0.1.0/tests/test_tc_f6_031_lint.py +130 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# METAR → IWXXM monorepo — apps/, packages/, vendor/
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# --- Secrets & local operator env (never commit) ---
|
|
6
|
+
.env
|
|
7
|
+
.env.*
|
|
8
|
+
!.env.example
|
|
9
|
+
prod.env
|
|
10
|
+
.envrc
|
|
11
|
+
|
|
12
|
+
# Per-workspace env overrides (apps/* and packages/*)
|
|
13
|
+
apps/**/.env
|
|
14
|
+
apps/**/.env.*
|
|
15
|
+
!apps/**/.env.example
|
|
16
|
+
packages/**/.env
|
|
17
|
+
packages/**/.env.*
|
|
18
|
+
!packages/**/.env.example
|
|
19
|
+
|
|
20
|
+
# Legacy env paths (pre-migration; remove after cutover)
|
|
21
|
+
backend/.env
|
|
22
|
+
auth/.env
|
|
23
|
+
frontend/.env
|
|
24
|
+
|
|
25
|
+
# --- Python (uv workspace: apps/backend, packages/*) ---
|
|
26
|
+
__pycache__/
|
|
27
|
+
*.py[codz]
|
|
28
|
+
*$py.class
|
|
29
|
+
*.so
|
|
30
|
+
.Python
|
|
31
|
+
build/
|
|
32
|
+
develop-eggs/
|
|
33
|
+
dist/
|
|
34
|
+
downloads/
|
|
35
|
+
eggs/
|
|
36
|
+
.eggs/
|
|
37
|
+
lib/
|
|
38
|
+
lib64/
|
|
39
|
+
parts/
|
|
40
|
+
sdist/
|
|
41
|
+
var/
|
|
42
|
+
wheels/
|
|
43
|
+
share/python-wheels/
|
|
44
|
+
*.egg-info/
|
|
45
|
+
.installed.cfg
|
|
46
|
+
*.egg
|
|
47
|
+
MANIFEST
|
|
48
|
+
*.manifest
|
|
49
|
+
*.spec
|
|
50
|
+
pip-log.txt
|
|
51
|
+
pip-delete-this-directory.txt
|
|
52
|
+
__pypackages__/
|
|
53
|
+
*.sage.py
|
|
54
|
+
.venv/
|
|
55
|
+
venv/
|
|
56
|
+
env/
|
|
57
|
+
ENV/
|
|
58
|
+
env.bak/
|
|
59
|
+
venv.bak/
|
|
60
|
+
.pdm-python
|
|
61
|
+
.pdm-build/
|
|
62
|
+
.pixi
|
|
63
|
+
|
|
64
|
+
# --- Node / pnpm workspace (apps/*, packages/*) ---
|
|
65
|
+
**/node_modules/
|
|
66
|
+
.pnp
|
|
67
|
+
.pnp.js
|
|
68
|
+
.pnpm-store/
|
|
69
|
+
npm-debug.log*
|
|
70
|
+
yarn-debug.log*
|
|
71
|
+
yarn-error.log*
|
|
72
|
+
pnpm-debug.log*
|
|
73
|
+
.eslintcache
|
|
74
|
+
*.tsbuildinfo
|
|
75
|
+
|
|
76
|
+
# --- Build & bundle outputs ---
|
|
77
|
+
**/dist/
|
|
78
|
+
**/dist-ssr/
|
|
79
|
+
**/build/
|
|
80
|
+
**/.vite/
|
|
81
|
+
**/.turbo/
|
|
82
|
+
**/.next/
|
|
83
|
+
**/.nuxt/
|
|
84
|
+
**/.output/
|
|
85
|
+
**/.svelte-kit/
|
|
86
|
+
|
|
87
|
+
# --- Test & coverage ---
|
|
88
|
+
.tox/
|
|
89
|
+
.nox/
|
|
90
|
+
.coverage
|
|
91
|
+
.coverage.*
|
|
92
|
+
.coverage-baseline.json
|
|
93
|
+
.cache
|
|
94
|
+
nosetests.xml
|
|
95
|
+
coverage.xml
|
|
96
|
+
*.cover
|
|
97
|
+
*.py.cover
|
|
98
|
+
htmlcov/
|
|
99
|
+
htmlcov_ann/
|
|
100
|
+
cover/
|
|
101
|
+
.hypothesis/
|
|
102
|
+
.pytest_cache/
|
|
103
|
+
.nyc_output
|
|
104
|
+
**/coverage/
|
|
105
|
+
|
|
106
|
+
# Playwright (apps/e2e and legacy frontend/)
|
|
107
|
+
**/test-results/
|
|
108
|
+
**/playwright-report/
|
|
109
|
+
**/blob-report/
|
|
110
|
+
**/.playwright/
|
|
111
|
+
**/playwright/.cache/
|
|
112
|
+
|
|
113
|
+
# --- Typecheck & lint caches ---
|
|
114
|
+
.mypy_cache/
|
|
115
|
+
.dmypy.json
|
|
116
|
+
dmypy.json
|
|
117
|
+
.pyre/
|
|
118
|
+
.pytype/
|
|
119
|
+
.ruff_cache/
|
|
120
|
+
cython_debug/
|
|
121
|
+
|
|
122
|
+
# --- IDE & OS ---
|
|
123
|
+
.vscode/
|
|
124
|
+
.idea/
|
|
125
|
+
*.swp
|
|
126
|
+
*.swo
|
|
127
|
+
*~
|
|
128
|
+
.DS_Store
|
|
129
|
+
Thumbs.db
|
|
130
|
+
|
|
131
|
+
# --- Docs site builds ---
|
|
132
|
+
docs/_build/
|
|
133
|
+
/site
|
|
134
|
+
|
|
135
|
+
# --- Local tooling binaries / artifacts ---
|
|
136
|
+
.tools/
|
|
137
|
+
.local/
|
|
138
|
+
|
|
139
|
+
# --- Cursor (local indexing prefs only; .cursorignore is tracked) ---
|
|
140
|
+
.cursorindexingignore
|
|
141
|
+
|
|
142
|
+
# --- Misc generated ---
|
|
143
|
+
*.log
|
|
144
|
+
*.db
|
|
145
|
+
*.local
|
|
146
|
+
local_settings.py
|
|
147
|
+
db.sqlite3
|
|
148
|
+
db.sqlite3-journal
|
|
149
|
+
instance/
|
|
150
|
+
.webassets-cache
|
|
151
|
+
.scrapy
|
|
152
|
+
.ipynb_checkpoints
|
|
153
|
+
profile_default/
|
|
154
|
+
ipython_config.py
|
|
155
|
+
celerybeat-schedule
|
|
156
|
+
celerybeat.pid
|
|
157
|
+
.abstra/
|
|
158
|
+
.pypirc
|
|
159
|
+
marimo/_static/
|
|
160
|
+
marimo/_lsp/
|
|
161
|
+
__marimo__/
|
|
162
|
+
|
|
163
|
+
# --- Legacy submodule / pre-migration paths (remove after M7 cutover) ---
|
|
164
|
+
# Vendor snapshots live under vendor/schemas/ once migrated (tracked, not ignored).
|
|
165
|
+
GIFTs/.venv/
|
|
166
|
+
GIFTs/.gifts/
|
|
167
|
+
GIFTs/build/
|
|
168
|
+
GIFTs/dist/
|
|
169
|
+
GIFTs/data/
|
|
170
|
+
GIFTs/.coverage
|
|
171
|
+
GIFTs/.pytest_cache/
|
|
172
|
+
GIFTs/.mypy_cache/
|
|
173
|
+
GIFTs/sfstns.db
|
|
174
|
+
GIFTs/driver.py
|
|
175
|
+
GIFTs/wiki/
|
|
176
|
+
|
|
177
|
+
# --- Rust / maturin (tac2iwxxm / iwxxm-validate PyO3) ---
|
|
178
|
+
packages/tac2iwxxm/rust/target/
|
|
179
|
+
packages/iwxxm-validate/rust/target/
|
|
180
|
+
|
|
181
|
+
# E10-34 — synced runtime schema subset (generated; MANIFEST.json is tracked)
|
|
182
|
+
packages/iwxxm-validate/src/iwxxm_validate/schemas/iwxxm/
|
|
183
|
+
packages/iwxxm-validate/src/iwxxm_validate/schemas/iwxxm-us/
|
|
184
|
+
packages/iwxxm-validate/src/iwxxm_validate/schemas/LAST_SYNC.json
|
|
185
|
+
|
|
186
|
+
# ADR-027 — xsdata pydantic trees are committed (T3.7); ignore only bytecode
|
|
187
|
+
packages/shared/src/metar_shared/iwxxm_xsd/**/__pycache__/
|
|
188
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tac-validate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: TAC parse gate and business-rule pack (F6 lint)
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: msgspec>=0.19
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# tac-validate
|
|
11
|
+
|
|
12
|
+
TAC parse gate and shared business-rule pack for F6 products. MIT licensed.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install tac-validate==0.1.0
|
|
18
|
+
# or from the monorepo workspace:
|
|
19
|
+
uv sync --package tac-validate
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Library
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from tac_validate import lint
|
|
26
|
+
|
|
27
|
+
report = lint("METAR KJFK ...=", product="METAR")
|
|
28
|
+
if not report.ok:
|
|
29
|
+
for issue in report.issues:
|
|
30
|
+
print(issue.code, issue.message)
|
|
31
|
+
for fix in report.fixes:
|
|
32
|
+
print(fix.code, fix.replacement)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## CLI
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
tac-validate --product METAR path/to/report.tac
|
|
39
|
+
tac-validate --product METAR --json path/to/report.tac
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Exit code `0` when lint `ok`; `1` when error-severity issues are present (or the file cannot be read).
|
|
43
|
+
|
|
44
|
+
Rule depth (E10-21): METAR/SPECI/TAF full checklist; SIGMET/AIRMET/VAA/TCA template+gates.
|
|
45
|
+
Citations live in `docs/domain/TAC_VALIDATION.md` — no Annex prose in the wheel.
|
|
46
|
+
|
|
47
|
+
See ADR-015 / ADR-016. No FastAPI/Supabase imports; HTTP maps msgspec → pydantic.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# tac-validate
|
|
2
|
+
|
|
3
|
+
TAC parse gate and shared business-rule pack for F6 products. MIT licensed.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install tac-validate==0.1.0
|
|
9
|
+
# or from the monorepo workspace:
|
|
10
|
+
uv sync --package tac-validate
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Library
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from tac_validate import lint
|
|
17
|
+
|
|
18
|
+
report = lint("METAR KJFK ...=", product="METAR")
|
|
19
|
+
if not report.ok:
|
|
20
|
+
for issue in report.issues:
|
|
21
|
+
print(issue.code, issue.message)
|
|
22
|
+
for fix in report.fixes:
|
|
23
|
+
print(fix.code, fix.replacement)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## CLI
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
tac-validate --product METAR path/to/report.tac
|
|
30
|
+
tac-validate --product METAR --json path/to/report.tac
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Exit code `0` when lint `ok`; `1` when error-severity issues are present (or the file cannot be read).
|
|
34
|
+
|
|
35
|
+
Rule depth (E10-21): METAR/SPECI/TAF full checklist; SIGMET/AIRMET/VAA/TCA template+gates.
|
|
36
|
+
Citations live in `docs/domain/TAC_VALIDATION.md` — no Annex prose in the wheel.
|
|
37
|
+
|
|
38
|
+
See ADR-015 / ADR-016. No FastAPI/Supabase imports; HTTP maps msgspec → pydantic.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tac-validate"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "TAC parse gate and business-rule pack (F6 lint)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"msgspec>=0.19",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
tac-validate = "tac_validate.cli:main"
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["hatchling>=1.25"]
|
|
17
|
+
build-backend = "hatchling.build"
|
|
18
|
+
|
|
19
|
+
[tool.hatch.build.targets.wheel]
|
|
20
|
+
packages = ["src/tac_validate"]
|
|
21
|
+
|
|
22
|
+
[tool.pytest.ini_options]
|
|
23
|
+
testpaths = ["tests"]
|
|
24
|
+
pythonpath = ["src"]
|
|
25
|
+
markers = [
|
|
26
|
+
"slow: longer local/CI smokes (clean-venv wheel installs)",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[tool.coverage.run]
|
|
30
|
+
branch = true
|
|
31
|
+
source = ["src/tac_validate"]
|
|
32
|
+
|
|
33
|
+
[tool.coverage.report]
|
|
34
|
+
fail_under = 95
|
|
35
|
+
show_missing = true
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
line-length = 120
|
|
39
|
+
|
|
40
|
+
[tool.ruff.lint]
|
|
41
|
+
select = ["E", "W", "F", "I"]
|
|
42
|
+
ignore = ["E501"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""TAC parse gate and business-rule pack (F6 lint)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from tac_validate.api import lint
|
|
6
|
+
from tac_validate.models import Fix, Issue, LintReport
|
|
7
|
+
from tac_validate.products import PRODUCTS
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
10
|
+
|
|
11
|
+
__all__ = ["PRODUCTS", "Fix", "Issue", "LintReport", "__version__", "lint"]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Public ``lint()`` entrypoint for TAC parse gate + rule pack."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from tac_validate.models import LintReport
|
|
6
|
+
from tac_validate.products import PRODUCTS
|
|
7
|
+
from tac_validate.rules import check_parse_gate, check_product_rules
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def lint(tac_text: str, *, product: str = "METAR") -> LintReport:
|
|
11
|
+
"""
|
|
12
|
+
Lint TAC text for ``product`` using the shared rule-pack skeleton.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
tac_text :
|
|
17
|
+
TAC report or fragment.
|
|
18
|
+
product :
|
|
19
|
+
One of AIRMET, METAR, SIGMET, SPECI, TAF, VAA, TCA.
|
|
20
|
+
|
|
21
|
+
Returns
|
|
22
|
+
-------
|
|
23
|
+
LintReport
|
|
24
|
+
``ok`` is ``False`` when any error-severity issue is present.
|
|
25
|
+
Optional ``fixes`` may suggest repairs (Q9=C).
|
|
26
|
+
"""
|
|
27
|
+
issues, fixes = check_parse_gate(tac_text, product)
|
|
28
|
+
if not any(i.severity == "error" for i in issues):
|
|
29
|
+
issues.extend(check_product_rules(tac_text, product))
|
|
30
|
+
|
|
31
|
+
ok = not any(i.severity == "error" for i in issues)
|
|
32
|
+
return LintReport(ok=ok, product=product, issues=issues, fixes=fixes)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__all__ = ["PRODUCTS", "lint"]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Command-line interface for ``tac-validate`` (F12)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Sequence
|
|
9
|
+
|
|
10
|
+
from tac_validate.api import lint
|
|
11
|
+
from tac_validate.codec import json_encoder
|
|
12
|
+
from tac_validate.products import PRODUCTS
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
16
|
+
parser = argparse.ArgumentParser(
|
|
17
|
+
prog="tac-validate",
|
|
18
|
+
description="Lint TAC text for F6 products (parse-gate + checklist/template gates).",
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"path",
|
|
22
|
+
type=Path,
|
|
23
|
+
help="Path to a TAC text file",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--product",
|
|
27
|
+
required=True,
|
|
28
|
+
choices=list(PRODUCTS),
|
|
29
|
+
help="F6 product id",
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--json",
|
|
33
|
+
action="store_true",
|
|
34
|
+
help="Emit LintReport as JSON on stdout",
|
|
35
|
+
)
|
|
36
|
+
return parser
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
40
|
+
"""
|
|
41
|
+
Run ``tac-validate`` CLI.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
argv :
|
|
46
|
+
Argument vector (defaults to ``sys.argv[1:]``).
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
int
|
|
51
|
+
``0`` when ``report.ok``; ``1`` on lint errors or I/O failure.
|
|
52
|
+
"""
|
|
53
|
+
parser = _build_parser()
|
|
54
|
+
args = parser.parse_args(list(argv) if argv is not None else None)
|
|
55
|
+
path: Path = args.path
|
|
56
|
+
try:
|
|
57
|
+
text = path.read_text(encoding="utf-8")
|
|
58
|
+
except OSError as exc:
|
|
59
|
+
print(f"error: cannot read {path}: {exc}", file=sys.stderr)
|
|
60
|
+
return 1
|
|
61
|
+
|
|
62
|
+
report = lint(text, product=args.product)
|
|
63
|
+
if args.json:
|
|
64
|
+
sys.stdout.write(json_encoder.encode(report).decode("utf-8"))
|
|
65
|
+
sys.stdout.write("\n")
|
|
66
|
+
else:
|
|
67
|
+
status = "ok" if report.ok else "fail"
|
|
68
|
+
print(f"{status} product={report.product} issues={len(report.issues)}")
|
|
69
|
+
for issue in report.issues:
|
|
70
|
+
span = ""
|
|
71
|
+
if issue.start is not None and issue.end is not None:
|
|
72
|
+
span = f" [{issue.start}:{issue.end}]"
|
|
73
|
+
print(f" {issue.severity}:{issue.code}{span} {issue.message}")
|
|
74
|
+
|
|
75
|
+
return 0 if report.ok else 1
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Reusable msgspec JSON codec for LintReport (ADR-016)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import msgspec
|
|
6
|
+
|
|
7
|
+
from tac_validate.models import LintReport
|
|
8
|
+
|
|
9
|
+
json_encoder = msgspec.json.Encoder()
|
|
10
|
+
json_decoder = msgspec.json.Decoder(LintReport)
|
|
11
|
+
|
|
12
|
+
__all__ = ["json_decoder", "json_encoder"]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""msgspec models for TAC lint issues and fixes (ADR-016 / Q9=C)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import msgspec
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Issue(msgspec.Struct, frozen=True):
|
|
9
|
+
"""
|
|
10
|
+
Structured TAC lint finding.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
severity :
|
|
15
|
+
``error``, ``warning``, or ``info``.
|
|
16
|
+
code :
|
|
17
|
+
Machine-readable rule id.
|
|
18
|
+
message :
|
|
19
|
+
Human-readable description.
|
|
20
|
+
location :
|
|
21
|
+
Optional token / field hint (e.g. ``wind``).
|
|
22
|
+
start :
|
|
23
|
+
Optional inclusive character offset into the TAC string.
|
|
24
|
+
end :
|
|
25
|
+
Optional exclusive character offset into the TAC string.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
severity: str
|
|
29
|
+
code: str
|
|
30
|
+
message: str
|
|
31
|
+
location: str | None = None
|
|
32
|
+
start: int | None = None
|
|
33
|
+
end: int | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Fix(msgspec.Struct, frozen=True):
|
|
37
|
+
"""
|
|
38
|
+
Optional repair suggestion for a lint finding.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
code :
|
|
43
|
+
Fix identifier (e.g. ``add_terminator``).
|
|
44
|
+
message :
|
|
45
|
+
Human-readable description.
|
|
46
|
+
replacement :
|
|
47
|
+
Suggested replacement fragment or full TAC.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
code: str
|
|
51
|
+
message: str
|
|
52
|
+
replacement: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class LintReport(msgspec.Struct, frozen=True):
|
|
56
|
+
"""
|
|
57
|
+
Result of ``lint()``.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
ok :
|
|
62
|
+
``True`` when no error-severity issues.
|
|
63
|
+
product :
|
|
64
|
+
Product id used for rule selection.
|
|
65
|
+
issues :
|
|
66
|
+
Structured findings.
|
|
67
|
+
fixes :
|
|
68
|
+
Optional repair suggestions.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
ok: bool
|
|
72
|
+
product: str
|
|
73
|
+
issues: list[Issue]
|
|
74
|
+
fixes: list[Fix] = msgspec.field(default_factory=list)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
__all__ = ["Fix", "Issue", "LintReport"]
|