use std::sync::Arc;
use clap::Parser;
use serde_json::json;
use memstead_base::render;
use memstead_schema::Schema;
use crate::CliError;
use crate::output::{ExitKind, print_json, print_markdown};
use crate::setup::CliContext;
#[derive(Parser, Debug)]
pub struct Args {
pub name: Option<String>,
#[arg(long)]
pub mem: Option<String>,
}
pub fn run(ctx: &CliContext, args: Args) -> anyhow::Result<()> {
let schema = resolve_schema(ctx, args.mem.as_deref())?;
let (schema_name, schema_version) = schema.id();
let schema_label = format!("{schema_name}@{schema_version}");
let md = match args.name.as_deref() {
None | Some("") => {
let mut out = render::render_type_catalog_markdown_for(&schema);
out.insert_str(0, &format!("**Schema:** `{schema_label}`\n\n"));
out
}
Some(name) => match schema.get_type(name) {
Some(td) => {
let mut out = render::render_type_info_markdown(&td);
out.insert_str(0, &format!("**Schema:** `{schema_label}`\n\n"));
out
}
None => {
let mut known: Vec<&str> = schema.types.keys().map(String::as_str).collect();
known.sort();
return Err(CliError::new(
ExitKind::Generic,
"UNKNOWN_ENTITY_TYPE",
format!(
"Unknown type: {name} (schema {schema_label}). \
Known types: {}",
known.join(", ")
),
)
.with_details(json!({
"name": name,
"schema_ref": schema_label,
"declared": known,
}))
.into());
}
},
};
if ctx.json {
print_json(&json!({
"markdown": md,
"schema": schema_label,
}))?;
} else {
print_markdown(&md);
}
Ok(())
}
fn resolve_schema(ctx: &CliContext, mem: Option<&str>) -> anyhow::Result<Arc<Schema>> {
let engine = match ctx.cli_engine() {
Ok(e) => e,
Err(_) => return Ok(Schema::builtin_default()),
};
let engine: memstead_base::Engine = engine.into_base();
let writable: Vec<&str> = engine.writable_mem_names();
let all_loaded: Vec<&str> = engine.mem_names();
let resolved_mem: &str = match mem {
Some(name) => {
if !all_loaded.contains(&name) {
let known = if all_loaded.is_empty() {
"no mems loaded".to_string()
} else {
format!("known mems: [{}]", all_loaded.join(", "))
};
return Err(CliError {
code: "UNKNOWN_MEM",
kind: ExitKind::NotFound,
message: format!("unknown mem: {name} — {known}"),
details: Some(json!({ "mem": name, "known_mems": all_loaded })),
}
.into());
}
name
}
None => match writable.len() {
0 => return Ok(Schema::builtin_default()),
1 => writable[0],
_ => {
let schemas = engine.schemas();
let schema_id = |v: &str| {
schemas
.get(v)
.map(|s| (s.manifest.name.clone(), s.version.clone()))
};
let first = schema_id(writable[0]);
let all_same = first.is_some() && writable.iter().all(|v| schema_id(v) == first);
if all_same {
writable[0]
} else {
return Err(CliError::new(
ExitKind::Validation,
"AMBIGUOUS_MEM",
format!(
"writable mems pin different schemas ([{}]) — pass `--mem <name>` to pick one",
writable.join(", ")
),
)
.with_details(json!({ "mems": writable }))
.into());
}
}
},
};
Ok(engine
.schemas()
.get(resolved_mem)
.cloned()
.unwrap_or_else(Schema::builtin_default))
}