feat(cli): render a consumer's own blueprint with aura graph <file> (#28)

aura graph was the last verb still sample-bound: its no-subcommand arm always
rendered the embedded r_sma_open.json, so a downstream researcher who authored
their own blueprint could introspect it as data but not RENDER it to see
mis-wiring before a run — the Construction-layer milestone payoff only half
realized. Give GraphCmd a first-positional blueprint (args_conflicts_with_subcommands
so it coexists with build/introspect/register), and branch the no-subcommand arm
on is_blueprint_file, mirroring dispatch_run: read the file, reload it via
blueprint_from_json against the project/std vocabulary, and render_html it; the
no-argument form still renders the embedded sample.

The library-packaging half (a downstream-linkable render crate) is dropped — the
ratified consumer surface is CLI-over-blueprint-data (decision on #28). The
nested-composite render is not a concern: the old graph.rs ascii render that
carried the unimplemented! was retired, render_html serializes via model_to_json
with no panic, and #26 is already closed.

closes #28
This commit is contained in:
2026-07-10 03:39:31 +02:00
parent 7cc986486f
commit 518c5e9017
+29 -8
View File
@@ -2038,7 +2038,10 @@ struct NewCmd {
}
#[derive(Args)]
#[command(args_conflicts_with_subcommands = true)]
struct GraphCmd {
/// A blueprint .json file to render (omit for the built-in sample).
blueprint: Option<String>,
#[command(subcommand)]
sub: Option<GraphSub>,
}
@@ -2665,14 +2668,32 @@ fn dispatch_chart(a: ChartCmd, env: &project::Env) {
fn dispatch_graph(a: GraphCmd, env: &project::Env) {
match a.sub {
None => {
let bp = blueprint_from_json(
include_str!("../examples/r_sma_open.json"),
&|t| env.resolve(t),
)
.expect("the shipped r-sma example reloads into a renderable blueprint");
print!("{}", render::render_html(&bp));
}
None => match is_blueprint_file(&a.blueprint) {
Some(path) => {
let doc = std::fs::read_to_string(path).unwrap_or_else(|e| {
eprintln!("aura: {path}: {e}");
std::process::exit(2);
});
let bp = blueprint_from_json(&doc, &|t| env.resolve(t)).unwrap_or_else(|e| {
let mut msg = graph_construct::blueprint_load_prose(&e);
if let Some(hint) = graph_construct::unresolved_namespace_hint(&e, env) {
msg.push_str("");
msg.push_str(&hint);
}
eprintln!("aura: {path}: {msg}");
std::process::exit(2);
});
print!("{}", render::render_html(&bp));
}
None => {
let bp = blueprint_from_json(
include_str!("../examples/r_sma_open.json"),
&|t| env.resolve(t),
)
.expect("the shipped r-sma example reloads into a renderable blueprint");
print!("{}", render::render_html(&bp));
}
},
Some(GraphSub::Build) => graph_construct::build_cmd(env),
Some(GraphSub::Introspect(i)) => graph_construct::introspect_cmd(i, env),
Some(GraphSub::Register { file }) => graph_construct::register_cmd(&file, env),