cargo_docs_md/lib.rs
1//! docs-md library interface for testing and reuse.
2//!
3//! This module exposes the core functionality of docs-md as a library,
4//! allowing integration tests and external tools to use the markdown
5//! generation capabilities programmatically.
6
7#![deny(missing_docs)]
8#![warn(clippy::pedantic)]
9#![warn(clippy::nursery)]
10
11use std::path::PathBuf;
12
13use clap::{Parser, Subcommand, ValueEnum};
14
15pub mod error;
16pub mod generator;
17pub mod linker;
18#[cfg(feature = "trace")]
19pub mod logger;
20pub mod multi_crate;
21pub mod parser;
22pub mod types;
23
24pub use crate::generator::{Generator, MarkdownCapture};
25pub use crate::linker::{LinkRegistry, slugify_anchor};
26#[cfg(feature = "trace")]
27use crate::logger::LogLevel;
28pub use crate::multi_crate::{
29 CrateCollection, MultiCrateContext, MultiCrateGenerator, MultiCrateParser, SearchIndex,
30 SearchIndexGenerator, UnifiedLinkRegistry,
31};
32
33/// Output format for the generated markdown documentation.
34///
35/// Controls how module files are organized in the output directory.
36#[derive(Debug, Clone, Copy, Default, ValueEnum)]
37pub enum OutputFormat {
38 /// Flat structure: all files in one directory.
39 ///
40 /// Module hierarchy is encoded in filenames using double underscores.
41 /// Example: `parent__child__grandchild.md`
42 #[default]
43 Flat,
44
45 /// Nested structure: directories mirror module hierarchy.
46 ///
47 /// Each module gets its own directory with an `index.md` file.
48 /// Example: `parent/child/grandchild/index.md`
49 Nested,
50}
51
52/// Cargo wrapper for subcommand invocation.
53///
54/// When invoked as `cargo docs-md`, cargo passes "docs-md" as the first argument.
55/// This wrapper handles that by making `docs-md` a subcommand that contains the real CLI.
56#[derive(Parser, Debug)]
57#[command(name = "cargo", bin_name = "cargo")]
58pub enum Cargo {
59 /// Generate per-module markdown from rustdoc JSON
60 #[command(name = "docs-md")]
61 DocsMd(Cli),
62}
63
64/// Top-level CLI for docs-md.
65#[derive(Parser, Debug)]
66#[command(
67 author,
68 version,
69 about = "Generate per-module markdown from rustdoc JSON",
70 args_conflicts_with_subcommands = true
71)]
72pub struct Cli {
73 #[command(subcommand)]
74 /// Subcommand to run
75 pub command: Option<Command>,
76
77 #[command(flatten)]
78 /// Generation options (used when no subcommand is specified)
79 pub args: GenerateArgs,
80
81 /// Logging verbosity level
82 ///
83 /// Controls the amount of diagnostic output. Use for debugging link
84 /// resolution issues or understanding the generation process.
85 #[cfg(feature = "trace")]
86 #[arg(long, value_enum, default_value = "off")]
87 pub log_level: LogLevel,
88
89 /// Enable logging to a file instead of stderr
90 ///
91 /// When set, logs are written to this file path instead of stderr.
92 /// Useful for capturing debug output without cluttering terminal.
93 #[cfg(feature = "trace")]
94 #[arg(long)]
95 pub log_file: Option<PathBuf>,
96}
97
98/// Available subcommands
99#[derive(Subcommand, Debug)]
100pub enum Command {
101 /// Build rustdoc JSON and generate markdown in one step.
102 ///
103 /// This runs `cargo +nightly doc` with JSON output, then generates
104 /// markdown documentation from the result. Requires nightly toolchain.
105 ///
106 /// Example: `cargo docs-md docs --primary-crate my_crate`
107 Docs(DocsArgs),
108}
109
110/// Arguments for the `docs` subcommand (build + generate).
111#[derive(Parser, Debug)]
112#[expect(
113 clippy::struct_excessive_bools,
114 reason = "Hm.. Cache lining optimization? Seems unnecessary for a CLI args struct."
115)]
116pub struct DocsArgs {
117 /// Output directory for generated markdown files.
118 ///
119 /// Defaults to `generated_docs/` in the current directory.
120 #[arg(short, long, default_value = "generated_docs")]
121 pub output: PathBuf,
122
123 /// Output format (flat or nested).
124 #[arg(short, long, value_enum, default_value_t = CliOutputFormat::Nested)]
125 pub format: CliOutputFormat,
126
127 /// Primary crate name for preferential link resolution.
128 ///
129 /// If not specified, attempts to detect from Cargo.toml.
130 #[arg(long)]
131 pub primary_crate: Option<String>,
132
133 /// Exclude private (non-public) items from the output.
134 ///
135 /// By default, all items are documented including private ones.
136 /// Enable this to only include public items.
137 #[arg(long, default_value_t = false)]
138 pub exclude_private: bool,
139
140 /// Include blanket trait implementations in the output.
141 #[arg(long, default_value_t = false)]
142 pub include_blanket_impls: bool,
143
144 /// Skip generating mdBook SUMMARY.md file.
145 #[arg(long, default_value_t = false)]
146 pub no_mdbook: bool,
147
148 /// Skip generating `search_index.json` file.
149 #[arg(long, default_value_t = false)]
150 pub no_search_index: bool,
151
152 /// Run cargo clean before building (full rebuild).
153 #[arg(long, default_value_t = false)]
154 pub clean: bool,
155
156 /// Additional arguments to pass to cargo doc.
157 ///
158 /// Example: `docs-md docs -- --all-features`
159 #[arg(last = true)]
160 pub cargo_args: Vec<String>,
161}
162
163/// Command-line arguments for direct generation (no subcommand).
164///
165/// The tool accepts input from two mutually exclusive sources:
166/// 1. A local rustdoc JSON file (`--path`)
167/// 2. A directory of rustdoc JSON files (`--dir`)
168#[derive(Parser, Debug, Default)]
169#[expect(
170 clippy::struct_excessive_bools,
171 reason = "Hm.. Cache lining optimization? Seems unnecessary for a CLI args struct."
172)]
173pub struct GenerateArgs {
174 /// Path to a local rustdoc JSON file.
175 ///
176 /// Generate this file with: `cargo doc --output-format json`
177 /// The JSON file will be in `target/doc/{crate_name}.json`
178 ///
179 /// Mutually exclusive with `--dir`.
180 #[arg(
181 short,
182 long,
183 required_unless_present_any = ["dir"],
184 conflicts_with = "dir"
185 )]
186 pub path: Option<PathBuf>,
187
188 /// Directory containing multiple rustdoc JSON files.
189 ///
190 /// Use this for multi-crate documentation generation. The tool will
191 /// scan the directory for all `*.json` files (rustdoc format) and
192 /// generate documentation for each crate with cross-crate linking.
193 ///
194 /// Generate JSON files with:
195 /// `RUSTDOCFLAGS='-Z unstable-options --output-format json' cargo +nightly doc`
196 ///
197 /// Mutually exclusive with `--path`.
198 #[arg(
199 long,
200 required_unless_present_any = ["path"],
201 conflicts_with = "path"
202 )]
203 pub dir: Option<PathBuf>,
204
205 /// Generate mdBook-compatible SUMMARY.md file.
206 ///
207 /// Only valid with `--dir` for multi-crate documentation.
208 /// Creates a `SUMMARY.md` file in the output directory that can be
209 /// used as the entry point for an mdBook documentation site.
210 #[arg(long, requires = "dir", default_value_t = false)]
211 pub mdbook: bool,
212
213 /// Generate `search_index.json` for client-side search.
214 ///
215 /// Only valid with `--dir` for multi-crate documentation.
216 /// Creates a `search_index.json` file containing all documented items,
217 /// which can be used with client-side search libraries like Fuse.js,
218 /// Lunr.js, or `FlexSearch`.
219 #[arg(long, requires = "dir", default_value_t = false)]
220 pub search_index: bool,
221
222 /// Primary crate name for preferential link resolution.
223 ///
224 /// When specified with `--dir`, links to items in this crate take
225 /// precedence over items with the same name in dependencies.
226 /// This helps resolve ambiguous links like `exit` to the intended
227 /// crate rather than `std::process::exit`.
228 #[arg(long, requires = "dir")]
229 pub primary_crate: Option<String>,
230
231 /// Output directory for generated markdown files.
232 ///
233 /// The directory will be created if it doesn't exist.
234 /// Defaults to `generated_docs/` in the current directory.
235 #[arg(short, long, default_value = "generated_docs")]
236 pub output: PathBuf,
237
238 /// Output format (flat or nested).
239 ///
240 /// - `flat`: All files in one directory
241 /// - `nested`: Directory hierarchy mirroring modules (default)
242 #[arg(short, long, value_enum, default_value_t = CliOutputFormat::Nested)]
243 pub format: CliOutputFormat,
244
245 /// Exclude private (non-public) items from the output.
246 ///
247 /// By default, all items are documented including `pub(crate)`,
248 /// `pub(super)`, and private items. Enable this to only include
249 /// public items.
250 #[arg(long, default_value_t = false)]
251 pub exclude_private: bool,
252
253 /// Include blanket trait implementations in the output.
254 ///
255 /// By default, blanket impls like `From`, `Into`, `TryFrom`, `TryInto`,
256 /// `Any`, `Borrow`, `BorrowMut`, and `ToOwned` are filtered out to reduce
257 /// noise. Enable this to include them in the documentation.
258 #[arg(long, default_value_t = false)]
259 pub include_blanket_impls: bool,
260}
261
262/// Backwards-compatible type alias for existing code.
263pub type Args = GenerateArgs;
264
265/// CLI-compatible output format enum (for clap `ValueEnum` derive).
266#[derive(Clone, Copy, Debug, Default, ValueEnum)]
267pub enum CliOutputFormat {
268 /// Flat structure with double-underscore separators in filenames.
269 #[default]
270 Flat,
271
272 /// Nested directory structure mirroring the module hierarchy.
273 Nested,
274}
275
276impl From<CliOutputFormat> for OutputFormat {
277 fn from(cli: CliOutputFormat) -> Self {
278 match cli {
279 CliOutputFormat::Flat => Self::Flat,
280
281 CliOutputFormat::Nested => Self::Nested,
282 }
283 }
284}