continuwuity/xtask/src/tasks/generate_docs/admin_commands.rs
2026-01-12 10:36:37 -05:00

112 lines
3.1 KiB
Rust

//! Generates documentation for the various commands that may be used in the admin room and server console.
//!
//! This generates one index page and several category pages, one for each of the direct subcommands of the top-level
//! `!admin` command. Those category pages then list all of the sub-subcommands.
use std::path::Path;
use askama::Template;
use clap::{Command, CommandFactory};
use conduwuit_admin::AdminCommand;
use crate::tasks::{TaskResult, generate_docs::FileOutput};
#[derive(askama::Template)]
#[template(path = "admin/index.md")]
/// The template for the index page, which links to all of the category pages.
struct Index {
categories: Vec<Category>
}
/// A direct subcommand of the top-level `!admin` command.
#[derive(askama::Template)]
#[template(path = "admin/category.md")]
struct Category {
name: String,
description: String,
commands: Vec<Subcommand>,
}
/// A second-or-deeper level subcommand of the `!admin` command.
struct Subcommand {
name: String,
description: String,
/// How deeply nested this command was in the original command tree.
/// This determines the header size used for it in the documentation.
depth: usize,
}
fn flatten_subcommands(command: &Command) -> Vec<Subcommand> {
fn flatten(
subcommands: &mut Vec<Subcommand>,
name_stack: &mut Vec<String>,
command: &Command
) {
let depth = name_stack.len();
name_stack.push(command.get_name().to_owned());
// do not include the root command
if depth > 0 {
let name = name_stack.join(" ");
let description = command
.get_long_about()
.or_else(|| command.get_about())
.map_or_else(|| "_(no description)_".to_owned(), ToString::to_string);
subcommands.push(
Subcommand {
name,
description,
depth,
}
);
}
for command in command.get_subcommands() {
flatten(subcommands, name_stack, command);
}
name_stack.pop();
}
let mut subcommands = Vec::new();
let mut name_stack = Vec::new();
flatten(&mut subcommands, &mut name_stack, command);
subcommands
}
pub(super) fn generate(out: &mut impl FileOutput) -> TaskResult<()> {
let admin_commands = AdminCommand::command();
let categories: Vec<_> = admin_commands
.get_subcommands()
.map(|command| {
Category {
name: command.get_name().to_owned(),
description: command.get_about().expect("categories should have a docstring").to_string(),
commands: flatten_subcommands(command),
}
})
.collect();
let root = Path::new("reference/admin/");
for category in &categories {
out.create_file(
root.join(&category.name).with_extension("md"),
category.render()?
);
}
out.create_file(
root.join("index.md"),
Index { categories }.render()?,
);
Ok(())
}