mod admin_commands; use std::{collections::HashMap, fs::File, io::Write, path::{Path, PathBuf}}; use cargo_metadata::MetadataCommand; use crate::tasks::TaskResult; trait FileOutput { fn create_file(&mut self, path: PathBuf, contents: String); } #[derive(Default)] struct FileQueue { queue: HashMap, } impl FileQueue { const GENERATED_HEADER: &'static str = "This file is generated by `cargo xtask generate-docs`. Do not edit."; /// Generate the header for the file at `path`. fn header_for(path: &Path) -> Option { Some(match path.extension()?.as_encoded_bytes() { b"md" => format!("", Self::GENERATED_HEADER), _ => return None }) } /// Write all queued files into the file tree rooted at `root`. fn write(self, root: &Path, dry_run: bool) -> std::io::Result<()> { for (path, contents) in self.queue { let path = root.join(&path); eprintln!("Writing {}", path.display()); if !dry_run { let mut file = File::create(&path)?; if let Some(header) = Self::header_for(&path) { writeln!(file, "{header}")?; } write!(file, "{contents}")?; if !contents.ends_with('\n') { writeln!(file)?; } } } Ok(()) } } impl FileOutput for FileQueue { fn create_file(&mut self, path: PathBuf, contents: String) { assert!(path.is_relative(), "path must be relative"); assert!(path.extension().is_some(), "path must not point to a directory"); assert!(!self.queue.contains_key(&path), "attempted to create an already created file {}", path.display()); self.queue.insert(path, contents); } } #[derive(clap::Args)] pub(crate) struct Args { /// The base path of the documentation. Defaults to `docs/` in the crate root. root: Option, } #[expect(clippy::needless_pass_by_value)] pub(super) fn run(common_args: crate::Args, task_args: Args) -> TaskResult<()> { let mut queue = FileQueue::default(); let metadata = MetadataCommand::new() .no_deps() .exec() .expect("should have been able to run cargo"); let root = task_args.root.unwrap_or_else(|| metadata.workspace_root.join_os("docs/")); admin_commands::generate(&mut queue)?; queue.write(&root, common_args.dry_run)?; Ok(()) }