#![warn(clippy::pedantic)]
use clap::{Parser, Subcommand};
use std::collections::BTreeMap;
use std::fs;
use std::path::PathBuf;
mod ast;
mod caixa;
mod category;
mod control_ast;
mod docs;
mod json_ast;
mod lined_ast;
mod patterns;
mod ruby_ast;
mod sexp_ast;
mod toml_ast;
mod xml_ast;
mod yaml;
mod yaml_ast;
#[derive(Parser)]
#[command(name = "pleme-doc-gen", version, about = "pleme-io actions docs + catalog generator")]
struct Cli {
#[arg(long, default_value = ".")]
actions_dir: PathBuf,
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Subcommand)]
enum Cmd {
Patterns,
Docs,
Index,
All {
#[arg(long)]
patterns_out: Option<PathBuf>,
},
Caixa {
#[arg(long)]
source: PathBuf,
#[arg(long, default_value = ".")]
out: PathBuf,
#[arg(long)]
force: bool,
},
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let actions = scan(&cli.actions_dir)?;
match cli.cmd {
Cmd::Patterns => {
print!("{}", patterns::emit(&actions));
}
Cmd::Docs => {
let written = docs::write_per_action(&cli.actions_dir, &actions)?;
eprintln!("wrote {written} per-action READMEs");
}
Cmd::Index => {
let path = cli.actions_dir.join("README.md");
fs::write(&path, docs::emit_index(&actions))?;
eprintln!("wrote {}", path.display());
}
Cmd::All { patterns_out } => {
let docs_written = docs::write_per_action(&cli.actions_dir, &actions)?;
eprintln!("wrote {docs_written} per-action READMEs");
let index_path = cli.actions_dir.join("README.md");
fs::write(&index_path, docs::emit_index(&actions))?;
eprintln!("wrote {}", index_path.display());
let patterns_text = patterns::emit(&actions);
if let Some(out) = patterns_out {
fs::write(&out, &patterns_text)?;
eprintln!("wrote {}", out.display());
} else {
print!("{patterns_text}");
}
}
Cmd::Caixa { source, out, force } => {
let src = fs::read_to_string(&source)?;
let written = caixa::render(&src, &out, force)?;
eprintln!("rendered {} artifact(s) from {}:", written.len(), source.display());
for f in &written {
eprintln!(" {}", f.display());
}
}
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct Action {
pub name: String,
pub description: String,
pub inputs: BTreeMap<String, InputSpec>,
pub outputs: BTreeMap<String, String>,
pub category: String,
pub backend: String,
}
#[derive(Debug, Clone, Default)]
pub struct InputSpec {
pub required: bool,
pub default: Option<String>,
pub description: Option<String>,
}
fn scan(dir: &std::path::Path) -> anyhow::Result<Vec<Action>> {
let mut out = vec![];
for entry in fs::read_dir(dir)? {
let entry = entry?;
let name = entry.file_name().into_string().unwrap_or_default();
if name.starts_with('_') || name.starts_with('.') {
continue;
}
let action_yml = entry.path().join("action.yml");
if !action_yml.exists() {
continue;
}
let yml = fs::read_to_string(&action_yml)?;
let parsed = yaml::parse(&yml);
let category = category::categorize(&name).into();
let backend = if entry.path().join("run.tlisp").exists() {
"tatara-lisp".into()
} else {
"shell".into()
};
out.push(Action {
name,
description: parsed.description,
inputs: parsed.inputs,
outputs: parsed.outputs,
category,
backend,
});
}
out.sort_by(|a, b| a.name.cmp(&b.name));
Ok(out)
}