pub mod cli;
pub mod discovery;
pub mod registry;
pub mod render;
pub mod template;
pub mod theme;
pub mod ui;
pub mod validate;
use std::fs;
use std::io::{self, Read};
use std::path::Path;
use anyhow::{Context, bail};
use cli::{Cli, Commands};
pub async fn run(cli: Cli) -> anyhow::Result<()> {
match cli.command {
Commands::Render(args) => cmd_render(args),
Commands::List(args) => cmd_list(args),
Commands::Schema(args) => cmd_schema(args),
Commands::Validate(args) => cmd_validate(args),
Commands::Ui(args) => ui::launch(args).await,
}
}
fn read_data(
data_path: Option<&Path>,
data_json: Option<&str>,
) -> anyhow::Result<serde_json::Value> {
match (data_path, data_json) {
(Some(path), _) => {
let text = if path.as_os_str() == "-" {
let mut buf = String::new();
io::stdin()
.read_to_string(&mut buf)
.context("reading stdin")?;
buf
} else {
fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?
};
serde_json::from_str(&text).context("parsing data JSON")
}
(_, Some(json_str)) => serde_json::from_str(json_str).context("parsing inline data JSON"),
_ => bail!("provide --data <FILE|-> or --data-json <JSON>"),
}
}
fn cmd_render(args: cli::RenderArgs) -> anyhow::Result<()> {
let registry = registry::TemplateRegistry::load(&args.template_pack)?;
let preset_name = args
.preset
.as_deref()
.or(args.template.as_deref())
.context("provide --preset or --template")?;
let data = read_data(args.data.as_deref(), args.data_json.as_deref())?;
if args.strict
&& let Some(preset) = registry.get_preset(preset_name)
{
validate::validate_data_against_schema(&data, &preset.schema)?;
}
let response = render::render_preset(®istry, preset_name, args.theme.as_deref(), &data)?;
let output = if args.compact {
serde_json::to_string(&response.card)?
} else {
serde_json::to_string_pretty(&response.card)?
};
match args.output {
Some(path) => {
fs::write(&path, &output).with_context(|| format!("writing {}", path.display()))?
}
None => println!("{}", output),
}
Ok(())
}
fn cmd_list(args: cli::ListArgs) -> anyhow::Result<()> {
let registry = registry::TemplateRegistry::load(&args.template_pack)?;
let presets = registry.presets.values();
if args.json {
let items: Vec<serde_json::Value> = presets
.filter(|p| args.category.as_deref().is_none_or(|c| p.category == c))
.map(|p| {
serde_json::json!({
"name": p.name,
"title": p.title,
"description": p.description,
"category": p.category,
"tags": p.tags,
})
})
.collect();
println!("{}", serde_json::to_string_pretty(&items)?);
} else {
for category in ®istry.categories {
if let Some(filter) = &args.category
&& &category.name != filter
{
continue;
}
println!("\n{} ({})", category.display_name, category.name);
for preset_name in &category.presets {
if let Some(p) = registry.get_preset(preset_name) {
println!(" {:<25} {}", p.name, p.description);
}
}
}
}
Ok(())
}
fn cmd_schema(args: cli::SchemaArgs) -> anyhow::Result<()> {
let registry = registry::TemplateRegistry::load(&args.template_pack)?;
let preset = registry
.get_preset(&args.template)
.with_context(|| format!("preset '{}' not found", args.template))?;
println!("{}", serde_json::to_string_pretty(&preset.schema)?);
Ok(())
}
fn cmd_validate(args: cli::ValidateArgs) -> anyhow::Result<()> {
let registry = registry::TemplateRegistry::load(&args.template_pack)?;
let preset = registry
.get_preset(&args.template)
.with_context(|| format!("preset '{}' not found", args.template))?;
let data = read_data(args.data.as_deref(), args.data_json.as_deref())?;
validate::validate_data_against_schema(&data, &preset.schema)?;
println!("Valid.");
Ok(())
}