forked from EduCraft/curriculum-project-hub
feat(spec+checker): slides/逐字稿 markdown 内容面 + assembleMarkdown step(ADR-0015)
给课程加两个 markdown 内容面:slides(大纲:h2 part → h3 小节 → ![]() 图 +
:::widget 教具)与逐字稿(口播)。直接 markdown+KaTeX 撰写,绕开 typst→md
公式转换(等于在 slides 面选 ADR-0014 的 R2)。
spec:
- RichContent.lean: content leaf 带 format(typst|markdown),新增 ContentFormat
归纳,RichContentRef 加 format 字段
- Export/Render.lean: Step 加 assembleMarkdown (field),钉执行语义
(同 shell 三边界 + 注入课程 h1 + 自包含收集引用图)
- Courseware.lean: ADR 区间 →0015
实现:
- cph-schema: x-cph-content 支持 true→.typ / 字符串→该扩展名(.md);
ContentField 带 ext;4 个 kind schema 各加可选 slides/transcript
- cph-model: Step::AssembleMarkdown{field} + manifest "assemble-markdown" 解析
- cph-check: run_markdown_assemble_target / target_is_markdown_assemble
(照 run_shell_target 形状:check 门控、注入 h1、按 [[parts]] 序拼、写盘后
收集  引用图进 build 根使产物自包含、引用图缺失属 build-过程错误
不入 6 类诊断);compile_targets 已 filter TypstCompile,纯 assemble 自然排除
- cph-cli: run_markdown_assemble_build 路由
- cph-typst: template_step 补 AssembleMarkdown => None 分支
测试:全 workspace 49 passed(新增 6 个 markdown 装配测试含图收集);
lake build 25 jobs 绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -110,6 +110,13 @@ fn run_build(
|
||||
return run_shell_build(path, engine, target);
|
||||
}
|
||||
|
||||
// A target whose steps assemble markdown (ADR-0015: slides outline / 逐字稿
|
||||
// transcript surfaces) is built by concatenating per-element `<field>.md`
|
||||
// files in parts order, not by compiling a typst template.
|
||||
if cph_check::target_is_markdown_assemble(path, target) {
|
||||
return run_markdown_assemble_build(path, engine, target);
|
||||
}
|
||||
|
||||
let out_path = out.unwrap_or_else(|| path.join("build").join(format!("{target}.pdf")));
|
||||
|
||||
let (pdf, report) = cph_check::build(path, engine, target);
|
||||
@@ -182,3 +189,43 @@ fn run_shell_build(path: &std::path::Path, engine: &Engine, target: &str) -> Exi
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
||||
/// Run an assemble-markdown target: concatenate each element's `<field>.md`
|
||||
/// (in parts order) and write the single-file artifact (ADR-0015). Like the
|
||||
/// shell path, this is opt-in — only on an explicit `cph build --target <name>`,
|
||||
/// and never in `check`.
|
||||
fn run_markdown_assemble_build(path: &std::path::Path, engine: &Engine, target: &str) -> ExitCode {
|
||||
eprintln!("assembling markdown target '{target}' (reading parts under {})", path.display());
|
||||
let report = cph_check::run_markdown_assemble_target(path, engine, target);
|
||||
print_diagnostics(&report.check);
|
||||
|
||||
if report.outcomes.is_empty() && !report.ok {
|
||||
if report.check.has_errors() {
|
||||
eprintln!("build refused: {} errors (fix the lesson first)", report.check.error_count());
|
||||
} else {
|
||||
eprintln!("build failed: target '{target}' has no assemble-markdown steps to run");
|
||||
}
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
for outcome in &report.outcomes {
|
||||
eprintln!(
|
||||
"assembled field '{}' from {} part(s)",
|
||||
outcome.field, outcome.parts_read
|
||||
);
|
||||
if let Some(out_rel) = &outcome.written {
|
||||
println!("wrote {} ({} bytes)", path.join(out_rel).display(), outcome.body.len());
|
||||
}
|
||||
if let Some(err) = &outcome.error {
|
||||
eprintln!("assemble failed: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
if report.ok {
|
||||
println!("assemble-markdown target '{target}' completed: {} step(s) ok", report.outcomes.len());
|
||||
ExitCode::SUCCESS
|
||||
} else {
|
||||
eprintln!("assemble-markdown target '{target}' failed");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user