Skip to main content

rbnx/cmd/
docs.rs

1// SPDX-License-Identifier: MulanPSL-2.0
2// `rbnx docs` — regenerate the mdBook contract + ROS IDL reference
3// (`docs/src/reference/{contracts,idl}.md`) from `capabilities/`.
4//
5// This is a developer-side step run inside the robonix source tree: it
6// reads the live `capabilities/` (contracts + lib IDL) through the same
7// loader codegen uses, and writes plain-markdown pages into the docs
8// (robonix-book) submodule. Those pages are committed there, so the
9// mdBook / GitHub Pages build compiles them with NO robonix environment —
10// no rbnx, no Rust, no `capabilities/`. Re-run after changing a contract
11// or IDL so the browsable reference stays in sync.
12
13use anyhow::{Context, Result};
14use colored::*;
15use robonix_cli::{Config, SourcePathKey};
16use std::path::{Path, PathBuf};
17use std::process::Command;
18
19use super::codegen::{build_codegen_cmd, locate_codegen_bin, run_cmd};
20
21pub async fn execute(config: Config, out_dir: Option<PathBuf>) -> Result<()> {
22    let root = config.resolve_source_path(SourcePathKey::Root)?;
23    let rust_root = config.resolve_source_path(SourcePathKey::RustRoot)?;
24    let capabilities_dir = config.resolve_source_path(SourcePathKey::Capabilities)?;
25    let interfaces_lib = config.resolve_source_path(SourcePathKey::InterfacesLib)?;
26
27    // Default into the docs submodule's reference dir. The generated files
28    // are committed there; mdBook / Pages need only the markdown.
29    let out = match out_dir {
30        Some(d) if d.is_absolute() => d,
31        Some(d) => root.join(d),
32        None => root.join("docs").join("src").join("reference"),
33    };
34    std::fs::create_dir_all(&out)
35        .with_context(|| format!("create reference dir {}", out.display()))?;
36
37    let stamp = version_stamp(&root);
38
39    let direct = locate_codegen_bin(&rust_root);
40    let cargo = if direct.is_none() {
41        Some(std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()))
42    } else {
43        None
44    };
45
46    println!(
47        "{} robonix-codegen --lang docs → {}",
48        "[docs]".bold(),
49        out.display()
50    );
51    println!("{} version stamp: {}", "[docs]".bold(), stamp);
52    // `rbnx docs` runs from the source tree, so keep codegen's scribe log with
53    // the generated output instead of dropping a `logs/` beside the sources.
54    let log_dir = out.parent().unwrap_or(&out).join("rbnx-build").join("logs");
55    let mut cmd = build_codegen_cmd(direct.as_ref(), cargo.as_deref(), &rust_root, &log_dir);
56    cmd.args(["--lang", "docs", "-I"])
57        .arg(&interfaces_lib)
58        .arg("--contracts")
59        .arg(&capabilities_dir)
60        .arg("-o")
61        .arg(&out)
62        .arg("--doc-stamp")
63        .arg(&stamp);
64    run_cmd("robonix-codegen docs", &mut cmd)?;
65
66    println!(
67        "{} wrote contracts.md + idl.md. Commit them into the docs repo — \
68         the mdBook / Pages build needs no robonix environment.",
69        "[docs]".green().bold()
70    );
71    Ok(())
72}
73
74/// `v<rbnx-version> · robonix commit <short-sha>[-dirty] · <commit-date>`,
75/// read from git in `root`. The commit is what the reference was generated
76/// from — always stated explicitly. Degrades gracefully when git or repo
77/// metadata is unavailable so the reference still carries *some* version.
78fn version_stamp(root: &Path) -> String {
79    let git = |args: &[&str]| -> Option<String> {
80        let out = Command::new("git")
81            .arg("-C")
82            .arg(root)
83            .args(args)
84            .output()
85            .ok()?;
86        if !out.status.success() {
87            return None;
88        }
89        let s = String::from_utf8(out.stdout).ok()?.trim().to_string();
90        (!s.is_empty()).then_some(s)
91    };
92    let sha = git(&["rev-parse", "--short", "HEAD"]).unwrap_or_else(|| "unknown".to_string());
93    let dirty = git(&["status", "--porcelain"]).is_some_and(|s| !s.is_empty());
94    let date = git(&["log", "-1", "--format=%cd", "--date=short"]).unwrap_or_default();
95    let ver = env!("CARGO_PKG_VERSION");
96    let dirty_tag = if dirty { "-dirty" } else { "" };
97    let date_part = if date.is_empty() {
98        String::new()
99    } else {
100        format!(" · {date}")
101    };
102    format!("v{ver} · commit {sha}{dirty_tag}{date_part}")
103}