arxo-codegen 0.4.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.
- arxo_codegen/__init__.py +5 -0
- arxo_codegen/__main__.py +7 -0
- arxo_codegen/active-outcomes.json +6837 -0
- arxo_codegen/check.py +158 -0
- arxo_codegen/check_active_canaries.py +262 -0
- arxo_codegen/check_api.py +76 -0
- arxo_codegen/check_conformance.py +414 -0
- arxo_codegen/check_matrix.py +131 -0
- arxo_codegen/check_packages.py +281 -0
- arxo_codegen/check_semantic_probes.py +293 -0
- arxo_codegen/check_xlsx.py +320 -0
- arxo_codegen/consumer_runner.py +316 -0
- arxo_codegen/core_support.py +203 -0
- arxo_codegen/coverage.py +341 -0
- arxo_codegen/deliver.py +180 -0
- arxo_codegen/examples/xlsx/pricing.lawir.json +409 -0
- arxo_codegen/examples/xlsx/pricing.xlsx.json +52 -0
- arxo_codegen/excel_plan.py +602 -0
- arxo_codegen/harness/verify/ci/gates/backends/check_backends.py +1248 -0
- arxo_codegen/harness/verify/ci/gates/backends/l1_fixtures.py +463 -0
- arxo_codegen/inert-nodes.json +62 -0
- arxo_codegen/inert.py +91 -0
- arxo_codegen/inputs.py +59 -0
- arxo_codegen/package.py +565 -0
- arxo_codegen/plan.py +4053 -0
- arxo_codegen/recertify_registry.py +100 -0
- arxo_codegen/runtime-cert.json +5259 -0
- arxo_codegen/runtime_cert.py +233 -0
- arxo_codegen/survey.py +272 -0
- arxo_codegen/target_runner.py +101 -0
- arxo_codegen/targets/bsl/cert.bsl +113 -0
- arxo_codegen/targets/bsl/emit.py +356 -0
- arxo_codegen/targets/bsl/json.bsl +243 -0
- arxo_codegen/targets/bsl/runner.bsl +210 -0
- arxo_codegen/targets/bsl/runtime.bsl +858 -0
- arxo_codegen/targets/cs/Cert.cs +90 -0
- arxo_codegen/targets/cs/Runner.cs +154 -0
- arxo_codegen/targets/cs/Runtime.cs +243 -0
- arxo_codegen/targets/cs/emit.py +223 -0
- arxo_codegen/targets/dart/cert.dart +99 -0
- arxo_codegen/targets/dart/emit.py +206 -0
- arxo_codegen/targets/dart/json.dart +166 -0
- arxo_codegen/targets/dart/runner.dart +154 -0
- arxo_codegen/targets/dart/runtime.dart +293 -0
- arxo_codegen/targets/erl/cert.erl +64 -0
- arxo_codegen/targets/erl/emit.py +216 -0
- arxo_codegen/targets/erl/rt.erl +238 -0
- arxo_codegen/targets/erl/runner.erl +130 -0
- arxo_codegen/targets/ex/cert.exs +62 -0
- arxo_codegen/targets/ex/emit.py +204 -0
- arxo_codegen/targets/ex/runner.exs +124 -0
- arxo_codegen/targets/ex/runtime.ex +227 -0
- arxo_codegen/targets/go/apiBase.go +483 -0
- arxo_codegen/targets/go/apicheck.go +97 -0
- arxo_codegen/targets/go/cert.go +218 -0
- arxo_codegen/targets/go/emit.py +1806 -0
- arxo_codegen/targets/go/evaluate.go +1826 -0
- arxo_codegen/targets/go/runner.go +38 -0
- arxo_codegen/targets/go/runtime.go +2612 -0
- arxo_codegen/targets/java/Cert.java +91 -0
- arxo_codegen/targets/java/Json.java +148 -0
- arxo_codegen/targets/java/Runner.java +151 -0
- arxo_codegen/targets/java/Runtime.java +243 -0
- arxo_codegen/targets/java/emit.py +198 -0
- arxo_codegen/targets/kt/Cert.kt +67 -0
- arxo_codegen/targets/kt/Json.kt +103 -0
- arxo_codegen/targets/kt/Runner.kt +126 -0
- arxo_codegen/targets/kt/Runtime.kt +196 -0
- arxo_codegen/targets/kt/emit.py +195 -0
- arxo_codegen/targets/lua/cert.lua +82 -0
- arxo_codegen/targets/lua/emit.py +250 -0
- arxo_codegen/targets/lua/json.lua +133 -0
- arxo_codegen/targets/lua/runner.lua +167 -0
- arxo_codegen/targets/lua/runtime.lua +541 -0
- arxo_codegen/targets/php/cert.php +84 -0
- arxo_codegen/targets/php/emit.py +229 -0
- arxo_codegen/targets/php/json.php +144 -0
- arxo_codegen/targets/php/runner.php +189 -0
- arxo_codegen/targets/php/runtime.php +660 -0
- arxo_codegen/targets/py/api_base.py +334 -0
- arxo_codegen/targets/py/apicheck.py +51 -0
- arxo_codegen/targets/py/cert.py +140 -0
- arxo_codegen/targets/py/emit.py +1140 -0
- arxo_codegen/targets/py/evaluate.py +980 -0
- arxo_codegen/targets/py/runner.py +38 -0
- arxo_codegen/targets/py/runtime.py +1634 -0
- arxo_codegen/targets/rb/cert.rb +73 -0
- arxo_codegen/targets/rb/emit.py +203 -0
- arxo_codegen/targets/rb/runner.rb +130 -0
- arxo_codegen/targets/rb/runtime.rb +255 -0
- arxo_codegen/targets/rs/Cargo.lock +142 -0
- arxo_codegen/targets/rs/Cargo.toml +16 -0
- arxo_codegen/targets/rs/cert.rs +106 -0
- arxo_codegen/targets/rs/emit.py +233 -0
- arxo_codegen/targets/rs/prefetch.rs +1 -0
- arxo_codegen/targets/rs/runner.rs +181 -0
- arxo_codegen/targets/rs/runtime.rs +448 -0
- arxo_codegen/targets/scala/Cert.scala +77 -0
- arxo_codegen/targets/scala/Json.scala +140 -0
- arxo_codegen/targets/scala/Runner.scala +121 -0
- arxo_codegen/targets/scala/Runtime.scala +269 -0
- arxo_codegen/targets/scala/emit.py +193 -0
- arxo_codegen/targets/swift/cert.swift +105 -0
- arxo_codegen/targets/swift/emit.py +231 -0
- arxo_codegen/targets/swift/json.swift +208 -0
- arxo_codegen/targets/swift/runner.swift +133 -0
- arxo_codegen/targets/swift/runtime.swift +528 -0
- arxo_codegen/targets/ts/apiBase.ts +250 -0
- arxo_codegen/targets/ts/apicheck.ts +37 -0
- arxo_codegen/targets/ts/cert.ts +96 -0
- arxo_codegen/targets/ts/emit.py +1214 -0
- arxo_codegen/targets/ts/evaluate.ts +894 -0
- arxo_codegen/targets/ts/package-lock.json +45 -0
- arxo_codegen/targets/ts/package.json +9 -0
- arxo_codegen/targets/ts/runner.ts +31 -0
- arxo_codegen/targets/ts/runtime.ts +1369 -0
- arxo_codegen/targets/xlsx/README.ru.md +176 -0
- arxo_codegen/targets/xlsx/emit.py +198 -0
- arxo_codegen/temporal.py +59 -0
- arxo_codegen/test_alias_types.py +171 -0
- arxo_codegen/test_api_contract.py +89 -0
- arxo_codegen/test_calendar_policy.py +48 -0
- arxo_codegen/test_check.py +49 -0
- arxo_codegen/test_check_active_canaries.py +96 -0
- arxo_codegen/test_check_conformance.py +132 -0
- arxo_codegen/test_check_semantic_probes.py +85 -0
- arxo_codegen/test_check_xlsx.py +52 -0
- arxo_codegen/test_consumer_runner.py +175 -0
- arxo_codegen/test_core_support.py +80 -0
- arxo_codegen/test_e0212_decision_time.py +63 -0
- arxo_codegen/test_head_aggregate.py +71 -0
- arxo_codegen/test_inert.py +143 -0
- arxo_codegen/test_instant_types.py +113 -0
- arxo_codegen/test_key_conflict_scope.py +92 -0
- arxo_codegen/test_key_conflict_targets.py +17 -0
- arxo_codegen/test_package_layout.py +163 -0
- arxo_codegen/test_plan_language_version.py +44 -0
- arxo_codegen/test_positions_refusal.py +43 -0
- arxo_codegen/test_recertify_registry.py +77 -0
- arxo_codegen/test_ruby_target.py +41 -0
- arxo_codegen/test_runtime_cert.py +83 -0
- arxo_codegen/test_secondary_key_conflict_scope.py +17 -0
- arxo_codegen/test_stage_r.py +108 -0
- arxo_codegen/test_standalone_inputs.py +71 -0
- arxo_codegen/test_support.py +150 -0
- arxo_codegen/test_survey_inputs.py +115 -0
- arxo_codegen-0.4.0.dist-info/LICENSE +202 -0
- arxo_codegen-0.4.0.dist-info/METADATA +1673 -0
- arxo_codegen-0.4.0.dist-info/RECORD +152 -0
- arxo_codegen-0.4.0.dist-info/WHEEL +5 -0
- arxo_codegen-0.4.0.dist-info/entry_points.txt +13 -0
- arxo_codegen-0.4.0.dist-info/top_level.txt +1 -0
arxo_codegen/package.py
ADDED
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Устанавливаемый пакет порождённой библиотеки (DECISION-0423 §2.7, шаг 5).
|
|
3
|
+
|
|
4
|
+
python3 package.py <ir.json> --target py --name NAME --version X.Y.Z --out DIR
|
|
5
|
+
python3 package.py <ir.json> --target ts --name @scope/name --version X.Y.Z --out DIR
|
|
6
|
+
python3 package.py <ir.json> --target go --name NAME --version X.Y.Z \\
|
|
7
|
+
--module example.com/org/name --out DIR
|
|
8
|
+
|
|
9
|
+
Модель, рантайм и API печатают те же эмиттеры, что и поставка `deliver.py`:
|
|
10
|
+
пакет меняет раскладку и импорты, но не семантику. Рантайм и модель лежат во
|
|
11
|
+
внутреннем подкаталоге (TypeScript — `src/internal/`, Python — `_internal/`;
|
|
12
|
+
в Go приватность даёт регистр имён, поэтому код — в корневом пакете модуля).
|
|
13
|
+
Наружу выходят функции API, `info()` с паспортом поставки и машинная карта
|
|
14
|
+
`api-map.json` «функция → вопрос или вход → StableId → правила → якоря
|
|
15
|
+
источника». Раннер и план ворот остаются в проверочном комплекте; у Go есть
|
|
16
|
+
команда `cmd/<пакет>-eval` с протоколом раннера (ответ — код 0, названный
|
|
17
|
+
отказ — `{"refused": …}` и код 3).
|
|
18
|
+
|
|
19
|
+
Имя пакета — параметр. Для канонов, которые публикует Arxo, принята семья
|
|
20
|
+
`-lib` (DECISION-0423 §7, выбор владельца): `@arxo/canon-<имя>-lib` (npm),
|
|
21
|
+
`arxo-canon-<имя>-lib` (PyPI), `github.com/arxohq/canon-<имя>-lib` (Go);
|
|
22
|
+
`@arxo/canon-<имя>` без суффикса — пакет самого канона (DECISION-0349 §8). Минимальные версии тулчейнов — константы целей
|
|
23
|
+
`TOOLCHAIN_MIN`, равные версиям проверок CI. TypeScript собирается при
|
|
24
|
+
упаковке собственным тулчейном цели: пакет без `dist/` не устанавливается.
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import hashlib
|
|
30
|
+
import json
|
|
31
|
+
import re
|
|
32
|
+
import shutil
|
|
33
|
+
import subprocess
|
|
34
|
+
import sys
|
|
35
|
+
import tempfile
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
HERE = Path(__file__).resolve().parent
|
|
39
|
+
sys.path.insert(0, str(HERE))
|
|
40
|
+
import deliver as D # noqa: E402
|
|
41
|
+
import plan as P # noqa: E402
|
|
42
|
+
from core_support import CoreKitError # noqa: E402
|
|
43
|
+
|
|
44
|
+
FORMAT = "law.codegen-package/0.1"
|
|
45
|
+
MAP_FORMAT = "law.codegen-api-map/0.1"
|
|
46
|
+
# Версия ABI поверхности API (DECISION-0423 §2.8): имена функций по языку,
|
|
47
|
+
# `Answer`, ошибки, конструкторы литерал-термов. Меняется вместе с
|
|
48
|
+
# несовместимой правкой поверхности, а не с версией генератора.
|
|
49
|
+
API_ABI = "law.codegen-api/1"
|
|
50
|
+
VALUE_FORMAT = "law.codegen-value/1"
|
|
51
|
+
TARGETS = ("py", "ts", "go")
|
|
52
|
+
EXAMPLE_DATE = "2026-01-01"
|
|
53
|
+
|
|
54
|
+
# Строки импортов плоской раскладки и их замены в пакете. Каждая строка
|
|
55
|
+
# обязана встретиться ровно один раз: незнакомая форма эмиттера — отказ
|
|
56
|
+
# упаковки, а не молча непереписанный импорт.
|
|
57
|
+
PY_REWRITES = {
|
|
58
|
+
"model.py": [("import runtime as RT", "from . import runtime as RT")],
|
|
59
|
+
"evaluate.py": [("import runtime as RT", "from . import runtime as RT"),
|
|
60
|
+
("import model as _MODEL", "from . import model as _MODEL")],
|
|
61
|
+
"api_base.py": [("import runtime as RT", "from . import runtime as RT"),
|
|
62
|
+
("from evaluate import evaluate as _evaluate", "from .evaluate import evaluate as _evaluate")],
|
|
63
|
+
"api.py": [("from api_base import (", "from ._internal.api_base import ("),
|
|
64
|
+
("import api_base as _base", "from ._internal import api_base as _base")],
|
|
65
|
+
}
|
|
66
|
+
PY_FLAT_IMPORT = re.compile(r"^\s*(import|from)\s+(runtime|model|evaluate|api_base|api)\b", re.M)
|
|
67
|
+
TS_INTERNAL = ("runtime.ts", "evaluate.ts", "apiBase.ts")
|
|
68
|
+
GO_FILES = ("runtime.go", "evaluate.go", "apiBase.go")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class PackageError(Exception):
|
|
72
|
+
"""Упаковка невозможна: названная причина, пакет не пишется."""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def sha(data: bytes) -> str:
|
|
76
|
+
return "sha256:" + hashlib.sha256(data).hexdigest()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def rewrite(text: str, pairs: list, where: str) -> str:
|
|
80
|
+
lines = text.split("\n")
|
|
81
|
+
for old, new in pairs:
|
|
82
|
+
hits = [i for i, line in enumerate(lines) if line.startswith(old)]
|
|
83
|
+
if len(hits) != 1:
|
|
84
|
+
raise PackageError(f"{where}: ожидалась одна строка «{old}», найдено {len(hits)}")
|
|
85
|
+
lines[hits[0]] = new + lines[hits[0]][len(old):]
|
|
86
|
+
return "\n".join(lines)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def generator_version() -> str:
|
|
90
|
+
"""Версия генератора: `pyproject.toml` рядом — исходники, иначе метаданные
|
|
91
|
+
установленного дистрибутива."""
|
|
92
|
+
pyproject = HERE / "pyproject.toml"
|
|
93
|
+
if pyproject.is_file():
|
|
94
|
+
import tomllib
|
|
95
|
+
return tomllib.loads(pyproject.read_text(encoding="utf-8"))["project"]["version"]
|
|
96
|
+
from importlib.metadata import version
|
|
97
|
+
return version("arxo-codegen")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def core_kit_digest() -> str | None:
|
|
101
|
+
"""SHA-256 манифеста настроенного комплекта ядра (у исходников без комплекта — нет)."""
|
|
102
|
+
import core_support
|
|
103
|
+
kit = core_support.core_kit_path()
|
|
104
|
+
return sha((Path(kit) / "manifest.json").read_bytes()) if kit else None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def go_package_name(ir: dict) -> str:
|
|
108
|
+
"""Имя пакета Go по умолчанию — последний сегмент имени канона без знаков."""
|
|
109
|
+
tail = str((ir.get("package") or {}).get("name") or "canon").split(".")[-1]
|
|
110
|
+
name = re.sub(r"[^a-z0-9]", "", tail.lower()) or "canon"
|
|
111
|
+
return name if not name[0].isdigit() else "p" + name
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def py_import_name(name: str) -> str:
|
|
115
|
+
"""Имя импорта Python по имени дистрибутива (PEP 503: `-`, `.` → `_`)."""
|
|
116
|
+
base = re.sub(r"[^A-Za-z0-9]+", "_", name.split("/")[-1]).strip("_").lower() or "canon"
|
|
117
|
+
return base if not base[0].isdigit() else "p" + base
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# --- карта API и пример ------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def function_names(lang: str, em, api: dict) -> tuple[dict, dict]:
|
|
123
|
+
"""Имена функций цели: вход → (факт, отрицательный факт), вопрос → (вопрос, строки)."""
|
|
124
|
+
inputs, questions = em.api_names(api["inputs"]), em.api_names(api["questions"])
|
|
125
|
+
if lang == "py":
|
|
126
|
+
fi = {k: (f"fact_{v}", f"negated_{v}") for k, v in inputs.items()}
|
|
127
|
+
fq = {k: (f"ask_{v}", f"rows_{v}") for k, v in questions.items()}
|
|
128
|
+
elif lang == "ts":
|
|
129
|
+
fi = {k: (f"facts.{v}", f"negated.{v}") for k, v in inputs.items()}
|
|
130
|
+
fq = {k: (f"ask.{v}", f"rows.{v}") for k, v in questions.items()}
|
|
131
|
+
else:
|
|
132
|
+
fi = {k: (f"Fact{v}", f"Negated{v}") for k, v in inputs.items()}
|
|
133
|
+
fq = {k: (f"Ask{v}", f"Rows{v}") for k, v in questions.items()}
|
|
134
|
+
return fi, fq
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def api_map(ir: dict, world: list, pl: dict, lang: str, em, distribution: dict, digest: str) -> dict:
|
|
138
|
+
api = pl.get("api") or {"questions": [], "inputs": []}
|
|
139
|
+
negation = "negation" in (pl.get("capabilities") or ())
|
|
140
|
+
rule_ids = {P._slug(n["id"]): n["id"] for doc in [ir, *world] for n in doc.get("nodes", [])
|
|
141
|
+
if n.get("kind") == "rule"}
|
|
142
|
+
fi, fq = function_names(lang, em, api)
|
|
143
|
+
functions = []
|
|
144
|
+
for q in api["questions"]:
|
|
145
|
+
functions.append({
|
|
146
|
+
"kind": "question", "function": fq[q["name"]][0], "rowsFunction": fq[q["name"]][1],
|
|
147
|
+
"name": q["name"], "predicate": q["pred"], "params": q["params"], "labels": q.get("labels", []),
|
|
148
|
+
"inputs": q.get("inputs", []),
|
|
149
|
+
"rules": [{"id": rule_ids.get(s["rule"], s["rule"]), "labels": s.get("labels", []),
|
|
150
|
+
"anchors": s.get("anchors", [])} for s in q.get("sources", [])]})
|
|
151
|
+
for i in api["inputs"]:
|
|
152
|
+
entry = {"kind": "input", "function": fi[i["name"]][0], "name": i["name"], "predicate": i["pred"],
|
|
153
|
+
"params": i["params"], "labels": i.get("labels", [])}
|
|
154
|
+
if negation:
|
|
155
|
+
entry["negatedFunction"] = fi[i["name"]][1]
|
|
156
|
+
functions.append(entry)
|
|
157
|
+
return {"format": MAP_FORMAT, "target": lang, "package": ir.get("package"), "semanticHash": digest,
|
|
158
|
+
"distribution": distribution, "functions": functions}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _example_value(param: str, kind: str, lang: str, q: str = "") -> str | None:
|
|
162
|
+
"""Литерал примера по объявленному типу; сущность одного типа — один id,
|
|
163
|
+
чтобы факты и вопрос говорили об одном объекте."""
|
|
164
|
+
local = re.sub(r"[^A-Za-z0-9]+", "-", param.split("#")[-1]).strip("-").lower() or "x"
|
|
165
|
+
go = lang == "go"
|
|
166
|
+
if kind == "text" and param == "urn:law:std#Text":
|
|
167
|
+
return f'{q}TextOf("пример")' if go else 'text("пример")'
|
|
168
|
+
if kind == "text":
|
|
169
|
+
return f'{q}EntityOf("urn:example:{local}-1")' if go else f'entity("urn:example:{local}-1")'
|
|
170
|
+
if kind == "money":
|
|
171
|
+
return f'{q}MoneyOf("100", "KZT")' if go else 'money("100", "KZT")'
|
|
172
|
+
if kind == "dec" and param == "urn:law:std#Integer":
|
|
173
|
+
return f"{q}IntegerOf(10)" if go else "integer(10)"
|
|
174
|
+
if kind == "dec" and param == "urn:law:std#Natural":
|
|
175
|
+
return f"{q}NaturalOf(10)" if go else "natural(10)"
|
|
176
|
+
if kind == "dec":
|
|
177
|
+
return f'{q}DecimalOf("10")' if go else 'decimal("10")'
|
|
178
|
+
if kind == "date":
|
|
179
|
+
return f'{q}DateOf("{EXAMPLE_DATE}")' if go else f'date("{EXAMPLE_DATE}")'
|
|
180
|
+
if kind == "bool":
|
|
181
|
+
return {"py": "boolean(True)", "ts": "boolean(true)", "go": f"{q}BooleanOf(true)"}[lang]
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _example_args(entry: dict, lang: str, q: str = "") -> list | None:
|
|
186
|
+
out = []
|
|
187
|
+
for param, kind in zip(entry["params"], entry["kinds"]):
|
|
188
|
+
value = _example_value(param, kind, lang, q)
|
|
189
|
+
if value is None:
|
|
190
|
+
return None
|
|
191
|
+
out.append(value)
|
|
192
|
+
return out
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def example(pl: dict, lang: str, em, import_path: str) -> str | None:
|
|
196
|
+
"""Исполняемый пример README: факты по входам первого вопроса с
|
|
197
|
+
синтезируемыми аргументами и сам вопрос; приёмка запускает его как есть."""
|
|
198
|
+
api = pl.get("api") or {"questions": [], "inputs": []}
|
|
199
|
+
fi, fq = function_names(lang, em, api)
|
|
200
|
+
inputs = {i["pred"]: i for i in api["inputs"]}
|
|
201
|
+
for q in api["questions"]:
|
|
202
|
+
q_args = _example_args(q, lang, "lib." if lang == "go" else "")
|
|
203
|
+
if q_args is None:
|
|
204
|
+
continue
|
|
205
|
+
facts = []
|
|
206
|
+
for pred in q.get("inputs", []):
|
|
207
|
+
i = inputs.get(pred)
|
|
208
|
+
args = _example_args(i, lang, "lib." if lang == "go" else "") if i else None
|
|
209
|
+
if args is not None:
|
|
210
|
+
facts.append((fi[i["name"]][0], args))
|
|
211
|
+
if len(facts) == 6:
|
|
212
|
+
break
|
|
213
|
+
used = sorted({re.match(r"(?:lib\.)?(\w+)\(", a).group(1) for _f, args in facts for a in args}
|
|
214
|
+
| {re.match(r"(?:lib\.)?(\w+)\(", a).group(1) for a in q_args})
|
|
215
|
+
ask = fq[q["name"]][0]
|
|
216
|
+
if lang == "py":
|
|
217
|
+
names = sorted({ask, *(f for f, _a in facts)}) + used
|
|
218
|
+
lines = [f"from {import_path} import {', '.join(names)}", "", "facts = ["]
|
|
219
|
+
lines += [f" {f}({', '.join(a)})," for f, a in facts]
|
|
220
|
+
lines += ["]", f'answer = {ask}(facts, "{EXAMPLE_DATE}"{"".join(", " + a for a in q_args)})',
|
|
221
|
+
"print(answer.truth_status, answer.evaluation_status)"]
|
|
222
|
+
return "\n".join(lines)
|
|
223
|
+
if lang == "ts":
|
|
224
|
+
names = ["ask", *(["facts"] if facts else [])] + used
|
|
225
|
+
lines = [f'import {{ {", ".join(names)} }} from "{import_path}";', "", "const known = ["]
|
|
226
|
+
lines += [f" {f}({', '.join(a)})," for f, a in facts]
|
|
227
|
+
lines += ["];", f'const answer = {ask}(known, "{EXAMPLE_DATE}"{"".join(", " + a for a in q_args)});',
|
|
228
|
+
'if (answer.resultKind === "PROPOSITION") console.log(answer.truthStatus, answer.evaluationStatus);']
|
|
229
|
+
return "\n".join(lines)
|
|
230
|
+
lines = ["package main", "", "import (", '\t"fmt"', "", f'\tlib "{import_path}"', ")", "",
|
|
231
|
+
"func main() {", "\tfacts := []lib.Fact{"]
|
|
232
|
+
lines += [f"\t\tlib.{f}({', '.join(a)})," for f, a in facts]
|
|
233
|
+
lines += ["\t}", f'\tanswer, err := lib.{ask}(facts, "{EXAMPLE_DATE}"{"".join(", " + a for a in q_args)})',
|
|
234
|
+
"\tif err != nil {", "\t\tpanic(err)", "\t}",
|
|
235
|
+
"\tfmt.Println(answer.TruthStatus, answer.EvaluationStatus)", "}"]
|
|
236
|
+
return "\n".join(lines)
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
FENCE = {"py": "python", "ts": "ts", "go": "go"}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def readme(ir: dict, lang: str, name: str, version: str, install: str, sample: str | None, mapping: dict) -> str:
|
|
244
|
+
package = ir.get("package") or {}
|
|
245
|
+
canon = f"{package.get('name')}@{package.get('version')}"
|
|
246
|
+
lines = [f"# {name}", "",
|
|
247
|
+
f"Исполняемая модель канона `{canon}` без движка правил: модуль порождён "
|
|
248
|
+
f"arxo-codegen из CLIR канона (профиль `core-executable`, DECISION-0188). "
|
|
249
|
+
"Ответ — статус истинности §62 и статус вычисления §175; доказательства и "
|
|
250
|
+
"хэшей воспроизводимости нет — за ними движок Arxo. Конструкция вне среза "
|
|
251
|
+
"генератора — названный отказ, а не другой ответ.", "",
|
|
252
|
+
"## Установка", "", "```sh", install, "```", ""]
|
|
253
|
+
if sample:
|
|
254
|
+
lines += ["## Пример", "", f"```{FENCE[lang]}", sample, "```", ""]
|
|
255
|
+
lines += ["## Функции", "", "| Функция | Вид | StableId |", "|---|---|---|"]
|
|
256
|
+
for f in mapping["functions"]:
|
|
257
|
+
kind = "вопрос" if f["kind"] == "question" else "вход"
|
|
258
|
+
lines.append(f"| `{f['function']}` | {kind} | `{f['predicate']}` |")
|
|
259
|
+
lines += ["", "Карта «функция → вопрос или вход → StableId → правила → якоря источника» — "
|
|
260
|
+
"`api-map.json`; паспорт поставки (канон, семантический хэш, мир, файлы) — "
|
|
261
|
+
"`info()`.", ""]
|
|
262
|
+
return "\n".join(lines)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# --- раскладки целей ---------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
def _emit(pl: dict, em) -> tuple[str, str]:
|
|
268
|
+
return em.emit(pl), em.emit_api(pl)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def layout_py(root: Path, em, pl: dict, name: str, version: str, ir: dict) -> dict:
|
|
272
|
+
imp = py_import_name(name)
|
|
273
|
+
pkg = root / "src" / imp
|
|
274
|
+
internal = pkg / "_internal"
|
|
275
|
+
internal.mkdir(parents=True)
|
|
276
|
+
model, api = _emit(pl, em)
|
|
277
|
+
sources = {n: (Path(em.__file__).parent / n).read_text(encoding="utf-8")
|
|
278
|
+
for n in ("runtime.py", "evaluate.py", "api_base.py")}
|
|
279
|
+
sources["model.py"] = model
|
|
280
|
+
for n, text in sources.items():
|
|
281
|
+
text = rewrite(text, PY_REWRITES.get(n, []), n)
|
|
282
|
+
if PY_FLAT_IMPORT.search(text):
|
|
283
|
+
raise PackageError(f"{n}: остался импорт плоской раскладки")
|
|
284
|
+
(internal / n).write_text(text, encoding="utf-8")
|
|
285
|
+
(internal / "__init__.py").write_text('"""Внутренняя часть пакета: рантайм, модель, единый вход."""\n',
|
|
286
|
+
encoding="utf-8")
|
|
287
|
+
api = rewrite(api, PY_REWRITES["api.py"], "api.py")
|
|
288
|
+
if PY_FLAT_IMPORT.search(api):
|
|
289
|
+
raise PackageError("api.py: остался импорт плоской раскладки")
|
|
290
|
+
(pkg / "api.py").write_text(api, encoding="utf-8")
|
|
291
|
+
(pkg / "py.typed").write_text("", encoding="utf-8")
|
|
292
|
+
package = ir.get("package") or {}
|
|
293
|
+
description = f"Исполняемая модель канона {package.get('name')} без движка (arxo-codegen)"
|
|
294
|
+
(pkg / "__init__.py").write_text("\n".join([
|
|
295
|
+
f'"""{name}: исполняемая модель канона {package.get("name")}@{package.get("version")} без движка.',
|
|
296
|
+
"",
|
|
297
|
+
"Функции `fact_*`, `ask_*`, `rows_*` и реестры `QUESTIONS`, `INPUTS` — из `api`;",
|
|
298
|
+
'`info()` отдаёт паспорт поставки (law.backend-delivery/1)."""',
|
|
299
|
+
"from .api import * # noqa: F401,F403",
|
|
300
|
+
"from .api import INPUTS, NEGATED, QUESTIONS # noqa: F401",
|
|
301
|
+
"",
|
|
302
|
+
"",
|
|
303
|
+
"def info() -> dict:",
|
|
304
|
+
' """Паспорт поставки: канон, семантический хэш §209, мир, профиль, файлы."""',
|
|
305
|
+
" from ._internal.passport import passport",
|
|
306
|
+
" return passport()",
|
|
307
|
+
""]), encoding="utf-8")
|
|
308
|
+
(root / "pyproject.toml").write_text("\n".join([
|
|
309
|
+
"[build-system]",
|
|
310
|
+
'requires = ["setuptools>=65"]',
|
|
311
|
+
'build-backend = "setuptools.build_meta"',
|
|
312
|
+
"",
|
|
313
|
+
"[project]",
|
|
314
|
+
f"name = {json.dumps(name)}",
|
|
315
|
+
f"version = {json.dumps(version)}",
|
|
316
|
+
f"description = {json.dumps(description, ensure_ascii=False)}",
|
|
317
|
+
'readme = "README.md"',
|
|
318
|
+
f'requires-python = ">={em.TOOLCHAIN_MIN}"',
|
|
319
|
+
"dependencies = []",
|
|
320
|
+
"",
|
|
321
|
+
"[tool.setuptools.packages.find]",
|
|
322
|
+
'where = ["src"]',
|
|
323
|
+
"",
|
|
324
|
+
"[tool.setuptools.package-data]",
|
|
325
|
+
f'{json.dumps(imp)} = ["py.typed", "api-map.json"]',
|
|
326
|
+
""]), encoding="utf-8")
|
|
327
|
+
return {"import": imp, "install": f"pip install {name}", "api_map": pkg / "api-map.json",
|
|
328
|
+
"passport": lambda text: (internal / "passport.py").write_text(
|
|
329
|
+
'"""Паспорт поставки (law.backend-delivery/1), вшитый при упаковке."""\n'
|
|
330
|
+
"import json as _json\n\n"
|
|
331
|
+
f"_PASSPORT = {text!r}\n\n\n"
|
|
332
|
+
"def passport() -> dict:\n"
|
|
333
|
+
" return _json.loads(_PASSPORT)\n", encoding="utf-8"),
|
|
334
|
+
"unhashed": {f"src/{imp}/_internal/passport.py"}}
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def layout_ts(root: Path, em, pl: dict, name: str, version: str, ir: dict) -> dict:
|
|
338
|
+
src = root / "src"
|
|
339
|
+
internal = src / "internal"
|
|
340
|
+
internal.mkdir(parents=True)
|
|
341
|
+
model, api = _emit(pl, em)
|
|
342
|
+
for n in TS_INTERNAL:
|
|
343
|
+
shutil.copyfile(Path(em.__file__).parent / n, internal / n)
|
|
344
|
+
(internal / "model.ts").write_text(model, encoding="utf-8")
|
|
345
|
+
if api.count('from "./apiBase"') != 3:
|
|
346
|
+
raise PackageError('api.ts: ожидались три импорта из "./apiBase"')
|
|
347
|
+
api = api.replace('from "./apiBase"', 'from "./internal/apiBase"')
|
|
348
|
+
if re.search(r'from "\./(runtime|model|evaluate)"', api):
|
|
349
|
+
raise PackageError("api.ts: остался импорт плоской раскладки")
|
|
350
|
+
(src / "api.ts").write_text(api, encoding="utf-8")
|
|
351
|
+
package = ir.get("package") or {}
|
|
352
|
+
(src / "index.ts").write_text("\n".join([
|
|
353
|
+
f"// {name}: исполняемая модель канона {package.get('name')}@{package.get('version')} без движка.",
|
|
354
|
+
"// Функции facts.*, ask.*, rows.* и реестры — из ./api; info() — паспорт поставки.",
|
|
355
|
+
'export * from "./api";',
|
|
356
|
+
'import { PASSPORT } from "./internal/passport";',
|
|
357
|
+
"",
|
|
358
|
+
"/** Паспорт поставки (law.backend-delivery/1): канон, семантический хэш §209, мир, файлы. */",
|
|
359
|
+
"export function info(): Record<string, unknown> {",
|
|
360
|
+
" return JSON.parse(PASSPORT);",
|
|
361
|
+
"}",
|
|
362
|
+
""]), encoding="utf-8")
|
|
363
|
+
(root / "tsconfig.json").write_text(json.dumps({"compilerOptions": {
|
|
364
|
+
"target": "es2022", "module": "commonjs", "lib": ["es2022"], "types": [], "strict": True,
|
|
365
|
+
"declaration": True, "rootDir": "src", "outDir": "dist"}, "include": ["src"]}, indent=2) + "\n",
|
|
366
|
+
encoding="utf-8")
|
|
367
|
+
(root / "package.json").write_text(json.dumps({
|
|
368
|
+
"name": name, "version": version,
|
|
369
|
+
"description": f"Исполняемая модель канона {package.get('name')} без движка (arxo-codegen)",
|
|
370
|
+
"main": "dist/index.js", "types": "dist/index.d.ts",
|
|
371
|
+
"exports": {".": {"types": "./dist/index.d.ts", "default": "./dist/index.js"},
|
|
372
|
+
"./api-map.json": "./api-map.json", "./package.json": "./package.json"},
|
|
373
|
+
"files": ["dist", "src", "api-map.json", "law-backend.json", "README.md"],
|
|
374
|
+
"engines": {"node": f">={em.TOOLCHAIN_MIN}"}, "sideEffects": False},
|
|
375
|
+
ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
376
|
+
|
|
377
|
+
def passport(text: str) -> None:
|
|
378
|
+
(internal / "passport.ts").write_text(
|
|
379
|
+
"// Паспорт поставки (law.backend-delivery/1), вшитый при упаковке.\n"
|
|
380
|
+
f"export const PASSPORT = {json.dumps(text, ensure_ascii=False)};\n", encoding="utf-8")
|
|
381
|
+
return {"import": name, "install": f"npm install {name}", "api_map": root / "api-map.json",
|
|
382
|
+
"passport": passport, "build": True,
|
|
383
|
+
"unhashed": {"src/internal/passport.ts", "dist/internal/passport.js", "dist/internal/passport.d.ts"}}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def layout_go(root: Path, em, pl: dict, name: str, version: str, ir: dict, module: str, gopkg: str) -> dict:
|
|
387
|
+
model, api = _emit(pl, em)
|
|
388
|
+
files = {n: (Path(em.__file__).parent / n).read_text(encoding="utf-8") for n in GO_FILES}
|
|
389
|
+
files["model.go"], files["api.go"] = model, api
|
|
390
|
+
for n, text in files.items():
|
|
391
|
+
if len(re.findall(r"^package main$", text, re.M)) != 1:
|
|
392
|
+
raise PackageError(f"{n}: ожидалась одна строка «package main»")
|
|
393
|
+
(root / n).write_text(re.sub(r"^package main$", f"package {gopkg}", text, count=1, flags=re.M),
|
|
394
|
+
encoding="utf-8")
|
|
395
|
+
(root / "eval.go").write_text("\n".join([
|
|
396
|
+
f"package {gopkg}", "",
|
|
397
|
+
"// EvaluateJSON — единый вход модели (DECISION-0423 §2.2): дело и вопрос в",
|
|
398
|
+
"// канонической форме §168/§172 → ответ профиля core-executable в JSON.",
|
|
399
|
+
"// Названный отказ среза — ошибка *RefusedError.",
|
|
400
|
+
"func EvaluateJSON(caseJSON, queryJSON []byte) (string, error) {",
|
|
401
|
+
"\treturn evaluate(caseJSON, queryJSON)",
|
|
402
|
+
"}", ""]), encoding="utf-8")
|
|
403
|
+
(root / "go.mod").write_text(f"module {module}\n\ngo {em.TOOLCHAIN_MIN}\n", encoding="utf-8")
|
|
404
|
+
command = root / "cmd" / f"{gopkg}-eval"
|
|
405
|
+
command.mkdir(parents=True)
|
|
406
|
+
(command / "main.go").write_text("\n".join([
|
|
407
|
+
f"// Команда {gopkg}-eval исполняет дело и вопрос канонической формы §168/§172",
|
|
408
|
+
"// и печатает ответ профиля core-executable: код 0 — ответ, код 3 — названный",
|
|
409
|
+
'// отказ среза в виде {"refused": текст}, как у раннера проверочного комплекта.',
|
|
410
|
+
"package main", "",
|
|
411
|
+
"import (", '\t"encoding/json"', '\t"errors"', '\t"fmt"', '\t"os"', "",
|
|
412
|
+
f'\tlib "{module}"', ")", "",
|
|
413
|
+
"func main() {",
|
|
414
|
+
"\tif len(os.Args) != 3 {",
|
|
415
|
+
f'\t\tfmt.Fprintln(os.Stderr, "использование: {gopkg}-eval дело.json вопрос.json")',
|
|
416
|
+
"\t\tos.Exit(2)",
|
|
417
|
+
"\t}",
|
|
418
|
+
"\tkase, err := os.ReadFile(os.Args[1])",
|
|
419
|
+
"\tif err != nil {", "\t\tfmt.Fprintln(os.Stderr, err)", "\t\tos.Exit(2)", "\t}",
|
|
420
|
+
"\tquery, err := os.ReadFile(os.Args[2])",
|
|
421
|
+
"\tif err != nil {", "\t\tfmt.Fprintln(os.Stderr, err)", "\t\tos.Exit(2)", "\t}",
|
|
422
|
+
"\tanswer, err := lib.EvaluateJSON(kase, query)",
|
|
423
|
+
"\tvar refused *lib.RefusedError",
|
|
424
|
+
"\tif errors.As(err, &refused) {",
|
|
425
|
+
'\t\tout, _ := json.Marshal(map[string]string{"refused": refused.Message})',
|
|
426
|
+
"\t\tfmt.Println(string(out))",
|
|
427
|
+
"\t\tos.Exit(3)",
|
|
428
|
+
"\t}",
|
|
429
|
+
"\tif err != nil {", "\t\tfmt.Fprintln(os.Stderr, err)", "\t\tos.Exit(1)", "\t}",
|
|
430
|
+
"\tfmt.Println(answer)",
|
|
431
|
+
"}", ""]), encoding="utf-8")
|
|
432
|
+
|
|
433
|
+
def passport(text: str) -> None:
|
|
434
|
+
(root / "passport.go").write_text("\n".join([
|
|
435
|
+
f"package {gopkg}", "",
|
|
436
|
+
"// Паспорт поставки (law.backend-delivery/1), вшитый при упаковке.",
|
|
437
|
+
f"const passportJSON = {json.dumps(text, ensure_ascii=False)}", "",
|
|
438
|
+
"// Info возвращает паспорт поставки: канон, семантический хэш §209, мир, файлы (JSON).",
|
|
439
|
+
"func Info() string { return passportJSON }", ""]), encoding="utf-8")
|
|
440
|
+
return {"import": module, "install": f"go get {module}@v{version}", "api_map": root / "api-map.json",
|
|
441
|
+
"passport": passport, "unhashed": {"passport.go"}}
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# --- сборка пакета -----------------------------------------------------------
|
|
445
|
+
|
|
446
|
+
def build_ts(root: Path, em) -> None:
|
|
447
|
+
modules = em.toolchain()
|
|
448
|
+
if modules is None:
|
|
449
|
+
raise PackageError("тулчейн TypeScript недоступен: пакет без dist/ не устанавливается")
|
|
450
|
+
result = subprocess.run([str(modules / ".bin" / "tsc"), "-p", str(root / "tsconfig.json")],
|
|
451
|
+
capture_output=True, text=True, check=False)
|
|
452
|
+
if result.returncode:
|
|
453
|
+
raise PackageError("сборка TypeScript отказала:\n" + (result.stdout + result.stderr)[-4000:])
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def package(ir: dict, lang: str, out: Path, *, name: str, version: str, module: str | None = None,
|
|
457
|
+
go_package: str | None = None, world: list | None = None, resources: dict | None = None) -> dict:
|
|
458
|
+
"""Пакет одной цели в новый каталог `out`; возвращает паспорт."""
|
|
459
|
+
if lang not in TARGETS:
|
|
460
|
+
raise PackageError(f"цель {lang}: пакет печатается только для {', '.join(TARGETS)}")
|
|
461
|
+
if not re.fullmatch(r"\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?", version):
|
|
462
|
+
raise PackageError(f"версия пакета не по SemVer: {version}")
|
|
463
|
+
if lang == "go" and not module:
|
|
464
|
+
raise PackageError("цель go: нужен --module (путь модуля Go)")
|
|
465
|
+
out = Path(out)
|
|
466
|
+
if out.exists():
|
|
467
|
+
raise PackageError(f"каталог пакета уже существует: {out}")
|
|
468
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
469
|
+
em = D.emitter(lang)
|
|
470
|
+
pl = P.build_plan(ir, world, resources=resources)
|
|
471
|
+
missing = D.missing_capabilities(pl, em)
|
|
472
|
+
if missing:
|
|
473
|
+
raise P.Refused(f"цель {lang} не поддерживает возможности плана: {', '.join(missing)}")
|
|
474
|
+
digest = D.semantic_hash(ir)
|
|
475
|
+
gopkg = go_package or go_package_name(ir)
|
|
476
|
+
distribution = {"format": FORMAT, "name": name, "version": version}
|
|
477
|
+
if lang == "go":
|
|
478
|
+
distribution.update(module=module, package=gopkg)
|
|
479
|
+
with tempfile.TemporaryDirectory(prefix="codegen-package-", dir=out.parent) as temp:
|
|
480
|
+
root = Path(temp) / "package"
|
|
481
|
+
root.mkdir()
|
|
482
|
+
if lang == "py":
|
|
483
|
+
spec = layout_py(root, em, pl, name, version, ir)
|
|
484
|
+
elif lang == "ts":
|
|
485
|
+
spec = layout_ts(root, em, pl, name, version, ir)
|
|
486
|
+
else:
|
|
487
|
+
spec = layout_go(root, em, pl, name, version, ir, module, gopkg)
|
|
488
|
+
mapping = api_map(ir, world or [], pl, lang, em, distribution, digest)
|
|
489
|
+
mapping_text = json.dumps(mapping, ensure_ascii=False, indent=1) + "\n"
|
|
490
|
+
spec["api_map"].write_text(mapping_text, encoding="utf-8")
|
|
491
|
+
if lang == "py":
|
|
492
|
+
(root / "api-map.json").write_text(mapping_text, encoding="utf-8")
|
|
493
|
+
sample = example(pl, lang, em, spec["import"])
|
|
494
|
+
(root / "README.md").write_text(readme(ir, lang, name, version, spec["install"], sample, mapping),
|
|
495
|
+
encoding="utf-8")
|
|
496
|
+
# Паспорт вшивается дважды одним текстом: заглушка до сборки (dist/
|
|
497
|
+
# TypeScript должен её видеть), настоящий — после хэширования файлов.
|
|
498
|
+
spec["passport"]("{}")
|
|
499
|
+
if spec.get("build"):
|
|
500
|
+
build_ts(root, em)
|
|
501
|
+
files = {p.relative_to(root).as_posix(): sha(p.read_bytes())
|
|
502
|
+
for p in sorted(root.rglob("*")) if p.is_file()
|
|
503
|
+
and p.relative_to(root).as_posix() not in spec["unhashed"]}
|
|
504
|
+
passport = {"format": D.FORMAT, "planVersion": pl["planVersion"], "target": lang,
|
|
505
|
+
"package": ir.get("package"), "semanticHash": digest, "world": pl.get("world", []),
|
|
506
|
+
"profile": getattr(em, "PROFILE", "core-executable"), "distribution": distribution,
|
|
507
|
+
# DECISION-0423 §2.8: провенанс артефакта, а не доказательство вычисления.
|
|
508
|
+
"generator": {"name": "arxo-codegen", "version": generator_version()},
|
|
509
|
+
"coreKit": {"manifestSha256": core_kit_digest()},
|
|
510
|
+
"capabilities": sorted(pl.get("capabilities") or ()),
|
|
511
|
+
"api": {"abi": API_ABI, "valueFormat": VALUE_FORMAT},
|
|
512
|
+
"files": files}
|
|
513
|
+
text = json.dumps(passport, ensure_ascii=False, indent=1) + "\n"
|
|
514
|
+
spec["passport"](text)
|
|
515
|
+
if spec.get("build"):
|
|
516
|
+
build_ts(root, em)
|
|
517
|
+
(root / "law-backend.json").write_text(text, encoding="utf-8")
|
|
518
|
+
shutil.move(str(root), str(out))
|
|
519
|
+
return passport
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def main() -> int:
|
|
523
|
+
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
|
524
|
+
ap.add_argument("ir", type=Path, help="CLIR пакета (*.lawir.json)")
|
|
525
|
+
ap.add_argument("--target", choices=TARGETS, required=True)
|
|
526
|
+
ap.add_argument("--name", required=True, help="имя пакета (npm, PyPI) или его человекочитаемое имя (Go)")
|
|
527
|
+
ap.add_argument("--version", required=True, help="версия пакета по SemVer")
|
|
528
|
+
ap.add_argument("--module", help="путь модуля Go")
|
|
529
|
+
ap.add_argument("--go-package", help="имя пакета Go (по умолчанию — из имени канона)")
|
|
530
|
+
ap.add_argument("--out", type=Path, required=True, help="новый каталог пакета")
|
|
531
|
+
ap.add_argument("--dep", type=Path, action="append", default=[], help="CLIR зависимости (можно повторять)")
|
|
532
|
+
ap.add_argument("--corpus-root", type=Path, help="явный корень corpus/clir и packs (адаптер law-dsl)")
|
|
533
|
+
ap.add_argument("--resources", type=Path, help="JSON: URI → {path, sha256}; пути относительно JSON")
|
|
534
|
+
ap.add_argument("--core-kit", type=Path, help="проверенный комплект поддержки ядра")
|
|
535
|
+
args = ap.parse_args()
|
|
536
|
+
if args.core_kit:
|
|
537
|
+
import os
|
|
538
|
+
os.environ["LAW_CODEGEN_CORE_KIT"] = str(args.core_kit.resolve())
|
|
539
|
+
ir = json.loads(args.ir.read_text(encoding="utf-8"))
|
|
540
|
+
try:
|
|
541
|
+
from inputs import corpus_resources, dependency_index, load_resources
|
|
542
|
+
extra = [json.loads(p.read_text(encoding="utf-8")) for p in args.dep]
|
|
543
|
+
index = P.world_index(args.corpus_root) if args.corpus_root else {}
|
|
544
|
+
dependency_index(extra)
|
|
545
|
+
world = P.resolve_world(ir, index, extra) if ir.get("imports") else []
|
|
546
|
+
resources = load_resources(args.resources) if args.resources else (
|
|
547
|
+
corpus_resources([ir, *world], args.corpus_root) if args.corpus_root else {})
|
|
548
|
+
passport = package(ir, args.target, args.out, name=args.name, version=args.version,
|
|
549
|
+
module=args.module, go_package=args.go_package, world=world, resources=resources)
|
|
550
|
+
except P.Refused as exc:
|
|
551
|
+
print(f"package: отказ плана — {exc}", file=sys.stderr)
|
|
552
|
+
return 2
|
|
553
|
+
except PackageError as exc:
|
|
554
|
+
print(f"package: {exc}", file=sys.stderr)
|
|
555
|
+
return 2
|
|
556
|
+
except CoreKitError as exc:
|
|
557
|
+
print(f"package: ошибка комплекта ядра — {exc}", file=sys.stderr)
|
|
558
|
+
return 1
|
|
559
|
+
print(f"package: {args.target} → {args.out} ({args.name} {args.version}, "
|
|
560
|
+
f"{passport['semanticHash'][:19]}…, файлов {len(passport['files'])})")
|
|
561
|
+
return 0
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
if __name__ == "__main__":
|
|
565
|
+
raise SystemExit(main())
|