diff --git a/Cargo.lock b/Cargo.lock index e775f6a16..7306b413d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -401,8 +401,7 @@ dependencies = [ [[package]] name = "clap-markdown" version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a2617956a06d4885b490697b5307ebb09fec10b088afc18c81762d848c2339" +source = "git+https://github.com/ChanTsune/clap-markdown.git?branch=issue%2F54#1d4d3b75577079c2064f3129602c53a82138936e" dependencies = [ "clap", ] diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index ded0bda38..2594443c2 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -13,6 +13,6 @@ release = false [dependencies] clap = { version = "4.5", features = ["derive"] } -clap-markdown = "0.1" +clap-markdown = { git = "https://github.com/ChanTsune/clap-markdown.git", branch = "issue/54" } clap_mangen = "0.2" portable-network-archive = { path = "../cli", default-features = false } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9e837dfa0..21d74b797 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,6 +1,6 @@ use std::{fs, path::PathBuf, process}; -use clap::{CommandFactory, Parser, builder::Resettable}; +use clap::{CommandFactory, Parser}; fn main() { if let Err(e) = run() { @@ -64,169 +64,19 @@ fn mangen(args: MangenArgs) -> Result<(), Box> { fn docgen(args: DocgenArgs) -> Result<(), Box> { let out_path = &args.output; - // Create parent directory if it doesn't exist - if let Some(parent) = out_path.parent() { - fs::create_dir_all(parent)?; - } - // Get the CLI command and rename to match the binary name - let mut cmd = portable_network_archive::cli::Cli::command().name("pna"); - - // Build the command to propagate global arguments to subcommands. - // This is necessary because clap-markdown iterates through subcommands - // and global argument references need to be resolved first. - cmd.build(); - - // After build(), clap sets display_name and bin_name on subcommands to include - // the parent name (e.g., display_name="pna-create", bin_name="pna create"). - // This causes two problems in clap-markdown: - // 1. Section headers show "pna pna-create" instead of "pna create" - // (because display_name is used in path building) - // 2. Usage lines show "pna pna create" instead of "pna create" - // (because render_usage() uses bin_name, then clap-markdown prepends parent path) - // - // We need to clear display_name on all subcommands so clap-markdown - // uses the command name instead of display_name for section headers. - clear_display_names(&mut cmd); + let cmd = portable_network_archive::cli::Cli::command().name("pna"); // Generate markdown documentation let markdown = clap_markdown::help_markdown_command(&cmd); - // Post-process to fix duplicate command paths. - // After build(), clap's render_usage() includes the full path (e.g., "pna create"), - // but clap-markdown also prepends the parent path, resulting in duplicates like: - // - "pna pna create" (for top-level subcommands) - // - "pna xattr pna xattr get" (for nested subcommands) - // - "pna-pna create" (in TOC links/anchors) - let markdown = fix_duplicate_command_paths(&markdown); + // Create a parent directory if it doesn't exist + if let Some(parent) = out_path.parent() { + fs::create_dir_all(parent)?; + } fs::write(out_path, &markdown)?; eprintln!("Markdown documentation generated: {}", out_path.display()); Ok(()) } - -/// Recursively clear display_name from all subcommands. -/// -/// After `Command::build()` is called, clap automatically sets display_name -/// on subcommands (e.g., "pna-create"). This causes clap-markdown to generate -/// incorrect section headers like "pna pna-create" instead of "pna create". -fn clear_display_names(cmd: &mut clap::Command) { - for sub in cmd.get_subcommands_mut() { - let mut owned = std::mem::take(sub); - owned = owned.display_name(Resettable::Reset); - clear_display_names(&mut owned); - *sub = owned; - } -} - -/// Fix duplicate command paths in the generated markdown. -/// -/// clap-markdown has a bug where it prepends the parent command path to the -/// usage string, but clap's render_usage() already includes the full path. -/// This results in duplicates like "pna xattr pna xattr get". -/// -/// This function removes these duplicated path segments using simple string -/// replacement, iterating until no more changes are made. -fn fix_duplicate_command_paths(markdown: &str) -> String { - let mut result = markdown.to_string(); - - // Keep replacing until no more changes - loop { - let prev = result.clone(); - - // Fix "pna pna " -> "pna " (simple duplicate) - result = result.replace("pna pna ", "pna "); - - // Fix "pna X pna X " patterns where X is a subcommand - // We enumerate known subcommand prefixes that might be duplicated - let subcommands = [ - "create", - "append", - "extract", - "list", - "split", - "concat", - "strip", - "xattr", - "complete", - "bug-report", - "experimental", - "help", - "stdio", - "delete", - "update", - "chown", - "chmod", - "acl", - "migrate", - "chunk", - "sort", - "diff", - "get", - "set", - ]; - - for sub in &subcommands { - // Fix patterns like "pna xattr pna xattr " -> "pna xattr " - let dup_pattern = format!("pna {sub} pna {sub} "); - let replacement = format!("pna {sub} "); - result = result.replace(&dup_pattern, &replacement); - - // Also handle end-of-command patterns (no trailing space) - let dup_pattern_end = format!("pna {sub} pna {sub}`"); - let replacement_end = format!("pna {sub}`"); - result = result.replace(&dup_pattern_end, &replacement_end); - } - - // Fix nested patterns like "pna experimental stdio pna experimental stdio" - for sub1 in &subcommands { - for sub2 in &subcommands { - let dup = format!("pna {sub1} {sub2} pna {sub1} {sub2}"); - let replacement = format!("pna {sub1} {sub2}"); - result = result.replace(&dup, &replacement); - } - } - - // Fix 3-level nested patterns - for sub1 in &["experimental", "help", "xattr", "acl", "chunk"] { - for sub2 in &subcommands { - for sub3 in &subcommands { - let dup = format!("pna {sub1} {sub2} {sub3} pna {sub1} {sub2} {sub3}"); - let replacement = format!("pna {sub1} {sub2} {sub3}"); - result = result.replace(&dup, &replacement); - } - } - } - - // Fix hyphenated anchors: "#pna-pna-" -> "#pna-" - result = result.replace("#pna-pna-", "#pna-"); - - // Fix hyphenated duplicates in anchors - for sub in &subcommands { - // Fix "#pna-X-pna-X" -> "#pna-X" - let dup = format!("#pna-{sub}-pna-{sub}"); - let replacement = format!("#pna-{sub}"); - result = result.replace(&dup, &replacement); - } - - // Fix 2-level hyphenated patterns - for sub1 in &subcommands { - for sub2 in &subcommands { - let dup = format!("-pna-{sub1}-{sub2}-pna-{sub1}-{sub2}"); - let replacement = format!("-pna-{sub1}-{sub2}"); - result = result.replace(&dup, &replacement); - - let dup2 = format!("#pna-{sub1}-pna-{sub1}-{sub2}"); - let replacement2 = format!("#pna-{sub1}-{sub2}"); - result = result.replace(&dup2, &replacement2); - } - } - - if result == prev { - break; - } - } - - result -}