feat(tsl-codegen): streamline generation workflow

Add explicit project-root output routing and require named input options with structured examples.

Remove the unused HTML metadata enrichment command and its dedicated tests.
This commit is contained in:
csh
2026-08-13 15:35:05 +08:00
parent 8315f1060a
commit f90faa10c3
5 changed files with 140 additions and 1730 deletions
+23 -33
View File
@@ -21,7 +21,7 @@ Function declaration fields:
tags optional list of Chinese keywords -> `<!-- tags: ... -->`
params required when the signature takes args; omit for nullary
returns required return type
example optional fenced tsl block, pasted verbatim
examples optional list of structured examples with desc/code/output fields
Each param: name/type/desc required; optional (bool) -> `可选。` prefix;
values (list of {value, desc}) -> a `name 取值` enum section.
@@ -29,6 +29,7 @@ Usage (run from repo root):
python tools/tsl-codegen/scripts/generate.py --file entry.yml
python tools/tsl-codegen/scripts/generate.py --file entry.json \
--scope my-project
python scripts/generate.py --file entry.json --root ../..
python tools/tsl-codegen/scripts/generate.py --dir recordings
"""
@@ -279,7 +280,6 @@ def validate_function(fn, where, *, returns_required, extra_fields=()):
"tags",
"params",
"returns",
"example",
"examples",
*extra_fields,
}
@@ -292,12 +292,6 @@ def validate_function(fn, where, *, returns_required, extra_fields=()):
non_empty_string(fn.get("returns"), f"{where}:缺少 returns")
elif "returns" in fn:
optional_draft_string(fn["returns"], f"{where}: returns")
require(
not ("example" in fn and "examples" in fn),
f"{where}example 和 examples 不能同时存在",
)
if "example" in fn:
non_empty_string(fn["example"], f"{where}: example")
if "examples" in fn:
validate_examples(fn["examples"], where)
return name
@@ -333,7 +327,6 @@ def validate_class_member(member, where):
"params",
"returns",
"modifiers",
"example",
"examples",
},
where,
@@ -600,14 +593,6 @@ def render_examples(fn, heading_level):
while lines and not lines[-1]:
lines.pop()
return lines
if fn.get("example"):
return [
f"{'#' * heading_level} 示例",
"",
"```tsl",
*fn["example"].rstrip("\n").split("\n"),
"```",
]
return []
@@ -822,7 +807,7 @@ def format_markdown(text):
return result.stdout
def output_path(data, scope):
def output_path(data, scope, root=None):
"""Build the leaf-page destination from the recording file's relative path."""
relative = data.get("path")
require(relative, "录入数据缺少 path")
@@ -854,7 +839,8 @@ def output_path(data, scope):
)
relative_path = Path(*recorded_path.parts)
return (
Path("skills/tsl-api-reference/references/codegen")
(Path(root) if root is not None else Path())
/ "skills/tsl-api-reference/references/codegen"
/ scope
/ relative_path.with_suffix(".md")
)
@@ -902,14 +888,14 @@ def gather_directory_inputs(directory, fmt):
return inputs
def prepare_input(in_path, input_format, scope):
def prepare_input(in_path, input_format, scope, root=None):
data = load_entries(in_path, input_format)
try:
rendered = render_page(data)
except GenerationError as exc:
raise GenerationError(f"录入数据校验失败:{exc}") from exc
try:
out_path = output_path(data, scope)
out_path = output_path(data, scope, root)
except GenerationError as exc:
raise GenerationError(f"输出路径无效:{exc}") from exc
try:
@@ -941,11 +927,14 @@ def find_output_collisions(prepared):
def main(argv=None):
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
if hasattr(sys.stderr, "reconfigure"):
sys.stderr.reconfigure(encoding="utf-8")
parser = ChineseArgumentParser(
description="从 YAML/JSON 录入文件生成 TSL API 文档",
usage=(
"%(prog)s [--help] [--scope SCOPE] [--format {json,yaml}] "
"(--file INPUT_FILE | --dir INPUT_DIR | INPUT_FILE)"
"%(prog)s [--help] [--root ROOT_DIR] [--scope SCOPE] "
"[--format {json,yaml}] "
"(--file INPUT_FILE | --dir INPUT_DIR)"
),
add_help=False,
allow_abbrev=False,
@@ -955,12 +944,6 @@ def main(argv=None):
action="help",
help="显示本帮助并退出(不提供 -h 短选项)",
)
parser.add_argument(
"legacy_input",
nargs="?",
metavar="INPUT_FILE",
help="已废弃,请使用 --file;暂时兼容 YAML/JSON 录入文件路径",
)
parser.add_argument(
"--file",
dest="input_file",
@@ -973,6 +956,11 @@ def main(argv=None):
metavar="INPUT_DIR",
help="批量生成目录中的直属 YAML/JSON 录入文件",
)
parser.add_argument(
"--root",
metavar="ROOT_DIR",
help="包含 skills 目录的项目根目录(默认:当前工作目录)",
)
parser.add_argument(
"--scope",
type=scope_name,
@@ -986,16 +974,16 @@ def main(argv=None):
)
args = parser.parse_args(argv)
input_modes = (args.legacy_input, args.input_file, args.input_dir)
input_modes = (args.input_file, args.input_dir)
if sum(value is not None for value in input_modes) != 1:
parser.error("必须且只能指定一种输入方式:INPUT_FILE、--file 或 --dir")
parser.error("必须且只能指定一种输入方式:--file 或 --dir")
is_batch = args.input_dir is not None
try:
if is_batch:
input_paths = gather_directory_inputs(Path(args.input_dir), args.format)
input_format = None
else:
in_path = Path(args.input_file or args.legacy_input)
in_path = Path(args.input_file)
if not in_path.is_file():
die(f"输入文件不存在或不是普通文件:{in_path}")
input_paths = [in_path]
@@ -1008,7 +996,9 @@ def main(argv=None):
input_errors = []
for in_path in input_paths:
try:
prepared.append(prepare_input(in_path, input_format, args.scope))
prepared.append(
prepare_input(in_path, input_format, args.scope, args.root)
)
except GenerationError as exc:
input_errors.append((in_path, exc))