forked from bai/curriculum-project-hub
feat(checker): align cph-typst + cph-check to export-as-build model (WU-D)
Integrate the WU-B (structured targets) and WU-C (numbly, config) changes. cph-typst: - World resolves any @preview/* package from the in-repo vendored dir (<render_dir>/vendor/typst-packages/preview/<name>/<version>/), fully offline — no typst-kit download. Proven: build_pdf_with_real_render compiles the real render package (which imports @preview/numbly:0.1.0) through the embedded engine, PDF out. - Driver threads each target's presentation config: looks up the TargetConfig by name, emits `config: (numbering: (heading: (...)))` (escaped) when an override is present, else `config: (:)` (render default). - target_precheck guards artifact/target: FileTree → blocking diagnostic (MVP is single-file, deferred per ADR-0009); --target not declared → blocking diagnostic; no-targets lesson still defaults through. cph-check: - Migrated to Lesson.targets: Vec<TargetConfig> (target_names() / target.name at the 6 sites). Pipeline unchanged (already matches ADR-0010 phases). Documented that !has_errors() implements Spec.Courseware.Legal (no error-level diagnostic). Workspace: fmt + clippy -D warnings + all tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,8 +24,8 @@ mod world;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use cph_diag::Diagnostic;
|
||||
use cph_model::Lesson;
|
||||
use cph_diag::{DiagCode, Diagnostic};
|
||||
use cph_model::{ArtifactKind, Lesson};
|
||||
use typst_kit::fonts::{self, FontStore};
|
||||
use typst_layout::PagedDocument;
|
||||
use typst_pdf::PdfOptions;
|
||||
@@ -78,6 +78,9 @@ impl Engine {
|
||||
/// spans landing in the generated driver are re-pointed with a hint marking
|
||||
/// them as driver-internal (a driver-gen bug indicator) rather than dropped.
|
||||
pub fn compile_check(&self, lesson: &Lesson, target: &str) -> Vec<Diagnostic> {
|
||||
if let Some(blocking) = target_precheck(lesson, target) {
|
||||
return blocking;
|
||||
}
|
||||
let world = self.world_for(lesson, target);
|
||||
let warned = typst::compile::<PagedDocument>(&world);
|
||||
|
||||
@@ -93,6 +96,9 @@ impl Engine {
|
||||
/// bytes; on fatal errors returns the mapped diagnostics. Compile warnings
|
||||
/// are not surfaced here (use [`Engine::compile_check`] for those).
|
||||
pub fn build_pdf(&self, lesson: &Lesson, target: &str) -> Result<Vec<u8>, Vec<Diagnostic>> {
|
||||
if let Some(blocking) = target_precheck(lesson, target) {
|
||||
return Err(blocking);
|
||||
}
|
||||
let world = self.world_for(lesson, target);
|
||||
let warned = typst::compile::<PagedDocument>(&world);
|
||||
let doc = match warned.output {
|
||||
@@ -120,6 +126,49 @@ impl Default for Engine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate a `(lesson, target)` request before compiling. Returns
|
||||
/// `Some(blocking_diagnostics)` when the request cannot be honored, or `None`
|
||||
/// when it is fine to proceed:
|
||||
///
|
||||
/// - **Unknown target** (the `--target` name isn't in `lesson.targets`): a
|
||||
/// `SchemaViolation` error — a target must be declared in the manifest to be
|
||||
/// built (ADR-0009: an export target is a declared build).
|
||||
/// - **`FileTree` artifact**: deferred for MVP. ADR-0009 makes the artifact kind
|
||||
/// decide assembly; only `SingleFile` (one bundled PDF) is implemented, so a
|
||||
/// `FileTree` target returns a clear `SchemaViolation` rather than wrong
|
||||
/// output. `SingleFile` proceeds to the normal one-PDF path.
|
||||
///
|
||||
/// A target with **no** declared `[targets.*]` config at all is *not* an error
|
||||
/// here: callers (e.g. `cph-check`) may compile-check a defaulted `"student"`
|
||||
/// target that the lesson never declared. Only a name that *is* declared but is
|
||||
/// `FileTree` is blocked; an undeclared name is the "unknown target" error.
|
||||
fn target_precheck(lesson: &Lesson, target: &str) -> Option<Vec<Diagnostic>> {
|
||||
match lesson.targets.iter().find(|t| t.name == target) {
|
||||
None if lesson.targets.is_empty() => {
|
||||
// Lesson declares no targets; the orchestrator compiles a defaulted
|
||||
// target. Proceed with render-package defaults (SingleFile).
|
||||
None
|
||||
}
|
||||
None => Some(vec![Diagnostic::error(
|
||||
DiagCode::SchemaViolation,
|
||||
format!("target '{target}' not declared in manifest"),
|
||||
)
|
||||
.with_hint(format!(
|
||||
"add a `[targets.{target}]` table to manifest.toml, or build a declared target"
|
||||
))]),
|
||||
Some(tc) => match tc.artifact {
|
||||
ArtifactKind::SingleFile => None,
|
||||
ArtifactKind::FileTree => Some(vec![Diagnostic::error(
|
||||
DiagCode::SchemaViolation,
|
||||
"file-tree artifact not yet implemented (MVP supports single-file)",
|
||||
)
|
||||
.with_hint(format!(
|
||||
"set `[targets.{target}].artifact` to \"single-file\" for now"
|
||||
))]),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a batch of typst diagnostics to `cph-diag` ones against `world`.
|
||||
fn map_all(world: &LessonWorld, diags: &[typst::diag::SourceDiagnostic]) -> Vec<Diagnostic> {
|
||||
diags
|
||||
|
||||
Reference in New Issue
Block a user