zad_cli/cli/docs.rs
1//! Implementation of the `zad docs [topic]` subcommand mandated by
2//! `OSS_SPEC.md` ยง12.3.
3//!
4//! Every topic doc under `docs/*.md` is embedded into the binary via
5//! `include_str!` so the exact text a contributor ships is the exact
6//! text an agent sees at runtime โ no filesystem lookups, no version
7//! skew between the installed binary and the source tree.
8//!
9//! Paths resolve through the `crates/zad-cli/docs` symlink, which
10//! points at `../../docs` (the canonical docs tree at the repo root).
11//! The symlink is resolved when cargo packages the crate, so the
12//! published tarball ships the doc files as real entries and the
13//! `cargo publish` verify step can compile the package outside the
14//! workspace. Don't replace the symlink with copies โ that would
15//! immediately drift from the canonical docs.
16
17use std::fmt::Write as _;
18
19use clap::Args;
20
21use zad::error::{Result, ZadError};
22
23#[derive(Debug, Args)]
24pub struct DocsArgs {
25 /// Topic name (without the `.md` extension). When omitted, lists the
26 /// available topics.
27 pub topic: Option<String>,
28}
29
30const TOPICS: &[(&str, &str)] = &[
31 ("architecture", include_str!("../../docs/architecture.md")),
32 ("configuration", include_str!("../../docs/configuration.md")),
33 (
34 "getting-started",
35 include_str!("../../docs/getting-started.md"),
36 ),
37 (
38 "troubleshooting",
39 include_str!("../../docs/troubleshooting.md"),
40 ),
41];
42
43pub fn run(args: DocsArgs) -> Result<()> {
44 match args.topic {
45 None => {
46 let mut out = String::new();
47 out.push_str("Available topics (run `zad docs <topic>` to read):\n");
48 for (name, _) in TOPICS {
49 let _ = writeln!(out, " {name}");
50 }
51 print!("{out}");
52 Ok(())
53 }
54 Some(topic) => match TOPICS.iter().find(|(n, _)| *n == topic) {
55 Some((_, body)) => {
56 print!("{body}");
57 Ok(())
58 }
59 None => Err(ZadError::Invalid(format!(
60 "no such docs topic: `{topic}`. Run `zad docs` to list available topics."
61 ))),
62 },
63 }
64}