use clap::CommandFactory;
use clap_complete::{Shell, generate_to};
use clap_mangen::Man;
use std::env;
use std::fs;
use std::io::Result;
use std::path::PathBuf;
#[path = "src/cli.rs"]
mod cli;
use cli::Cli;
fn generate_completions(outdir: &std::ffi::OsString) -> Result<()> {
let mut cmd = Cli::command();
for shell in [
Shell::Bash,
Shell::Fish,
Shell::Zsh,
Shell::PowerShell,
Shell::Elvish,
] {
generate_to(shell, &mut cmd, "arity", outdir)?;
}
let completions_dir = PathBuf::from("target/completions");
fs::create_dir_all(&completions_dir)?;
let outdir_path = PathBuf::from(outdir);
let bash_src = outdir_path.join("arity.bash");
let fish_src = outdir_path.join("arity.fish");
let zsh_src = outdir_path.join("_arity");
if bash_src.exists() {
fs::copy(&bash_src, completions_dir.join("arity.bash"))?;
}
if fish_src.exists() {
fs::copy(&fish_src, completions_dir.join("arity.fish"))?;
}
if zsh_src.exists() {
fs::copy(&zsh_src, completions_dir.join("_arity"))?;
}
Ok(())
}
fn generate_cli_markdown() -> Result<()> {
let is_packaging = env::current_dir()
.ok()
.and_then(|p| p.to_str().map(|s| s.contains("/target/package/")))
.unwrap_or(false);
if is_packaging {
return Ok(());
}
let cmd = Cli::command();
let docs_dir = PathBuf::from("docs/reference");
if !docs_dir.exists() {
return Ok(());
}
let opts = clap_markdown::MarkdownOptions::default()
.show_footer(false)
.show_table_of_contents(false);
let markdown = clap_markdown::help_markdown_command_custom(&cmd, &opts);
let mut document = String::new();
document.push_str("---\n");
document.push_str("title: CLI Reference\n");
document.push_str("description: >-\n Comprehensive reference for the Arity CLI, including all commands and options.\n");
document.push_str("---\n\n");
document.push_str(&markdown);
let output_path = docs_dir.join("cli.qmd");
fs::write(&output_path, &document)?;
println!("Generated CLI markdown: {output_path:?}");
Ok(())
}
fn format_see_also(refs: &[String]) -> String {
let formatted: Vec<String> = refs.iter().map(|r| format!("\\fB{}\\fR(1)", r)).collect();
format!(".SH \"SEE ALSO\"\n{}\n", formatted.join(", "))
}
fn generate_man_pages() -> Result<()> {
let out_dir = PathBuf::from("target/man");
fs::create_dir_all(&out_dir)?;
let cmd = Cli::command();
let subcommand_names: Vec<String> = cmd
.get_subcommands()
.filter(|s| s.get_name() != "help")
.map(|s| format!("arity-{}", s.get_name()))
.collect();
let man = Man::new(cmd.clone());
let mut buffer = Vec::new();
man.render(&mut buffer)?;
let main_content =
String::from_utf8_lossy(&buffer).into_owned() + &format_see_also(&subcommand_names);
fs::write(out_dir.join("arity.1"), main_content.as_bytes())?;
for subcommand in cmd.get_subcommands() {
let subcommand_name = subcommand.get_name();
if subcommand_name == "help" {
continue; }
let name = format!("arity-{}", subcommand_name);
let man = Man::new(subcommand.clone().version(env!("CARGO_PKG_VERSION"))).title(&name);
let mut buffer = Vec::new();
man.render(&mut buffer)?;
let content = String::from_utf8_lossy(&buffer);
let fixed_content = content
.replace(
&format!("{} \\-", subcommand_name),
&format!("{} \\-", name),
)
.replace(
&format!("\\fB{}\\fR", subcommand_name),
&format!("\\fBarity {}\\fR", subcommand_name),
)
.replace(
&format!("{}\\-", subcommand_name),
&format!("arity\\-{}\\-", subcommand_name),
);
let mut see_also_refs: Vec<String> = vec!["arity".to_string()];
see_also_refs.extend(subcommand_names.iter().filter(|n| *n != &name).cloned());
let with_see_also = fixed_content + &format_see_also(&see_also_refs);
fs::write(
out_dir.join(format!("{}.1", name)),
with_see_also.as_bytes(),
)?;
}
Ok(())
}
fn main() -> Result<()> {
if let Some(outdir) = env::var_os("OUT_DIR") {
generate_completions(&outdir)?;
}
generate_man_pages()?;
generate_cli_markdown()?;
println!("cargo:rerun-if-changed=src/cli.rs");
println!("cargo:rerun-if-changed=build.rs");
Ok(())
}