From 2edb4b2601045e1f2361a5858e32233f3656a6d1 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Tue, 13 Oct 2026 23:28:56 -0400 Subject: [PATCH] testkit: corpus --part NAME --design PATH writes a part's .solidcraft design Opens a corpus part in the app or over MCP without solidcraft-cli. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm --- crates/testkit/src/bin/corpus.rs | 27 +++++++++++++++++++++++++-- xtask/src/main.rs | 3 ++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/crates/testkit/src/bin/corpus.rs b/crates/testkit/src/bin/corpus.rs index f1e1bc5..1ba5bdb 100644 --- a/crates/testkit/src/bin/corpus.rs +++ b/crates/testkit/src/bin/corpus.rs @@ -1,26 +1,39 @@ //! `corpus [--part NAME] [--no-write]`: see [`modelcraft_testkit::runner`]; run it as //! `cargo xtask corpus`. +//! +//! `corpus --part NAME --design PATH` writes that part's `.solidcraft` design to `PATH` instead +//! (its recipe run, or its shape generated), for opening the part in the app or over MCP. #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] #![forbid(unsafe_code)] +use std::path::Path; use std::process::ExitCode; +const USAGE: &str = "usage: corpus [--part NAME] [--no-write] | corpus --part NAME --design PATH"; + fn main() -> ExitCode { let args: Vec = std::env::args().skip(1).collect(); let mut only = None; + let mut design = None; let mut write = true; let mut it = args.iter(); while let Some(a) = it.next() { match a.as_str() { "--part" => only = it.next().map(String::as_str), + "--design" => design = it.next().map(String::as_str), "--no-write" => write = false, other => { - eprintln!("error: unknown argument `{other}`\nusage: corpus [--part NAME] [--no-write]"); + eprintln!("error: unknown argument `{other}`\n{USAGE}"); return ExitCode::FAILURE; } } } - match modelcraft_testkit::runner::run(&modelcraft_testkit::corpus::workspace_root(), only, write) { + let result = match (design, only) { + (Some(path), Some(name)) => write_design(name, Path::new(path)), + (Some(_), None) => Err(format!("--design needs --part\n{USAGE}")), + (None, _) => modelcraft_testkit::runner::run(&modelcraft_testkit::corpus::workspace_root(), only, write), + }; + match result { Ok(()) => ExitCode::SUCCESS, Err(e) => { eprintln!("error: {e}"); @@ -28,3 +41,13 @@ fn main() -> ExitCode { } } } + +/// Write the design of the corpus part `name` to `path`. +fn write_design(name: &str, path: &Path) -> Result<(), String> { + let parts = modelcraft_testkit::corpus::parts().map_err(|e| e.to_string())?; + let part = parts.iter().find(|p| p.name == name).ok_or_else(|| format!("no corpus part named `{name}`"))?; + let bytes = modelcraft_testkit::corpus::design_bytes(part).map_err(|e| e.to_string())?; + std::fs::write(path, bytes).map_err(|e| format!("{}: {e}", path.display()))?; + eprintln!("corpus: wrote {} ({name})", path.display()); + Ok(()) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 5defcdc..c99f93b 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -36,7 +36,8 @@ commands: tests, and the benchmarks) and the doc tests alongside: the CI heavy job corpus [--part NAME] [--no-write] build every meshing test corpus part (tests/corpus, analytic shapes), tessellate - and mesh it, and write the results to docs/meshing/corpus.md (release build) + and mesh it, and write the results to docs/meshing/corpus.md (release build); + --part NAME --design PATH writes that part's .solidcraft design instead oracle [--case NAME]... [--bless NAME|all]... [--quick] [--no-write] run the verification and validation cases (tests/oracle, docs/verification.md) against their references and fixtures; write docs/oracle.md after a full run;