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;
22#[cfg(feature = "source-parsing")]
23pub mod source;
24pub mod types;
25pub mod utils;
26
27pub use crate::generator::{Generator, MarkdownCapture, RenderConfig, SourceConfig};
28pub use crate::linker::{AnchorUtils, LinkRegistry};
29#[cfg(feature = "trace")]
30use crate::logger::LogLevel;
31pub use crate::multi_crate::{
32 CrateCollection, MultiCrateContext, MultiCrateGenerator, MultiCrateParser, SearchIndex,
33 SearchIndexGenerator, UnifiedLinkRegistry,
34};
35
36/// Output format for the generated markdown documentation.
37///
38/// Controls how module files are organized in the output directory.
39#[derive(Debug, Clone, Copy, Default, ValueEnum)]
40pub enum OutputFormat {
41 /// Flat structure: all files in one directory.
42 ///
43 /// Module hierarchy is encoded in filenames using double underscores.
44 /// Example: `parent__child__grandchild.md`
45 #[default]
46 Flat,
47
48 /// Nested structure: directories mirror module hierarchy.
49 ///
50 /// Each module gets its own directory with an `index.md` file.
51 /// Example: `parent/child/grandchild/index.md`
52 Nested,
53}
54
55/// Cargo wrapper for subcommand invocation.
56///
57/// When invoked as `cargo docs-md`, cargo passes "docs-md" as the first argument.
58/// This wrapper handles that by making `docs-md` a subcommand that contains the real CLI.
59#[derive(Parser, Debug)]
60#[command(name = "cargo", bin_name = "cargo")]
61pub enum Cargo {
62 /// Generate per-module markdown from rustdoc JSON
63 #[command(name = "docs-md")]
64 DocsMd(Cli),
65}
66
67/// Top-level CLI for docs-md.
68#[derive(Parser, Debug)]
69#[command(
70 author,
71 version,
72 about = "Generate per-module markdown from rustdoc JSON",
73 args_conflicts_with_subcommands = true
74)]
75pub struct Cli {
76 #[command(subcommand)]
77 /// Subcommand to run
78 pub command: Option<Command>,
79
80 #[command(flatten)]
81 /// Generation options (used when no subcommand is specified)
82 pub args: GenerateArgs,
83
84 /// Logging verbosity level
85 ///
86 /// Controls the amount of diagnostic output. Use for debugging link
87 /// resolution issues or understanding the generation process.
88 #[cfg(feature = "trace")]
89 #[arg(long, value_enum, default_value = "off")]
90 pub log_level: LogLevel,
91
92 /// Enable logging to a file instead of stderr
93 ///
94 /// When set, logs are written to this file path instead of stderr.
95 /// Useful for capturing debug output without cluttering terminal.
96 #[cfg(feature = "trace")]
97 #[arg(long)]
98 pub log_file: Option<PathBuf>,
99}
100
101/// Available subcommands
102#[derive(Subcommand, Debug)]
103pub enum Command {
104 /// Build rustdoc JSON and generate markdown in one step.
105 ///
106 /// This runs `cargo +nightly doc` with JSON output, then generates
107 /// markdown documentation from the result. Requires nightly toolchain.
108 ///
109 /// Example: `cargo docs-md docs --primary-crate my_crate`
110 Docs(DocsArgs),
111
112 /// Collect dependency sources to a local directory.
113 ///
114 /// Copies source code from `~/.cargo/registry/src/` into a local
115 /// `.source_{timestamp}/` directory for parsing and documentation.
116 ///
117 /// Example: `cargo docs-md collect-sources --include-dev`
118 #[cfg(feature = "source-parsing")]
119 CollectSources(CollectSourcesArgs),
120}
121
122/// Arguments for the `docs` subcommand (build + generate).
123#[derive(Parser, Debug)]
124#[expect(
125 clippy::struct_excessive_bools,
126 reason = "Hm.. Cache lining optimization? Seems unnecessary for a CLI args struct."
127)]
128pub struct DocsArgs {
129 /// Output directory for generated markdown files.
130 ///
131 /// Defaults to `generated_docs/` in the current directory.
132 #[arg(short, long, default_value = "generated_docs")]
133 pub output: PathBuf,
134
135 /// Output format (flat or nested).
136 #[arg(short, long, value_enum, default_value_t = CliOutputFormat::Nested)]
137 pub format: CliOutputFormat,
138
139 /// Primary crate name for preferential link resolution.
140 ///
141 /// If not specified, attempts to detect from Cargo.toml.
142 #[arg(long)]
143 pub primary_crate: Option<String>,
144
145 /// Exclude private (non-public) items from the output.
146 ///
147 /// By default, all items are documented including private ones.
148 /// Enable this to only include public items.
149 #[arg(long, default_value_t = false)]
150 pub exclude_private: bool,
151
152 /// Include blanket trait implementations in the output.
153 #[arg(long, default_value_t = false)]
154 pub include_blanket_impls: bool,
155
156 /// Skip generating mdBook SUMMARY.md file.
157 #[arg(long, default_value_t = false)]
158 pub no_mdbook: bool,
159
160 /// Skip generating `search_index.json` file.
161 #[arg(long, default_value_t = false)]
162 pub no_search_index: bool,
163
164 /// Run cargo clean before building (full rebuild).
165 #[arg(long, default_value_t = false)]
166 pub clean: bool,
167
168 // === RenderConfig toggles ===
169 /// Minimum number of items before generating a table of contents.
170 ///
171 /// Modules with fewer items than this threshold won't have a TOC.
172 /// Default: 10
173 #[arg(long, default_value_t = 10)]
174 pub toc_threshold: usize,
175
176 /// Disable quick reference tables at the top of modules.
177 #[arg(long, default_value_t = false)]
178 pub no_quick_reference: bool,
179
180 /// Disable grouping impl blocks by category (Derive, Conversion, etc.).
181 #[arg(long, default_value_t = false)]
182 pub no_group_impls: bool,
183
184 /// Hide trivial derive implementations (Clone, Copy, Debug, etc.).
185 #[arg(long, default_value_t = false)]
186 pub hide_trivial_derives: bool,
187
188 /// Disable method-level anchors for deep linking.
189 #[arg(long, default_value_t = false)]
190 pub no_method_anchors: bool,
191
192 /// Include source file locations for items.
193 #[arg(long, default_value_t = false)]
194 pub source_locations: bool,
195
196 /// Include full method documentation instead of first-line summaries.
197 ///
198 /// By default, only the first paragraph of method docs is shown in impl blocks.
199 /// Enable this to include the complete documentation for each method.
200 #[arg(long, default_value_t = false)]
201 pub full_method_docs: bool,
202
203 /// Additional arguments to pass to cargo doc.
204 ///
205 /// Example: `docs-md docs -- --all-features`
206 #[arg(last = true)]
207 pub cargo_args: Vec<String>,
208}
209
210/// Arguments for the `collect-sources` subcommand.
211#[cfg(feature = "source-parsing")]
212#[derive(Parser, Debug)]
213pub struct CollectSourcesArgs {
214 /// Output directory for collected sources.
215 ///
216 /// If not specified, creates `.source_{timestamp}/` in the workspace root.
217 #[arg(short, long)]
218 pub output: Option<PathBuf>,
219
220 /// Include dev-dependencies in collection.
221 ///
222 /// By default, only regular dependencies are collected.
223 #[arg(long, default_value_t = false)]
224 pub include_dev: bool,
225
226 /// Dry run - show what would be collected without copying.
227 #[arg(long, default_value_t = false)]
228 pub dry_run: bool,
229
230 /// Path to Cargo.toml (defaults to current directory).
231 #[arg(long)]
232 pub manifest_path: Option<PathBuf>,
233}
234
235/// Command-line arguments for direct generation (no subcommand).
236///
237/// The tool accepts input from two mutually exclusive sources:
238/// 1. A local rustdoc JSON file (`--path`)
239/// 2. A directory of rustdoc JSON files (`--dir`)
240#[derive(Parser, Debug, Default)]
241#[expect(
242 clippy::struct_excessive_bools,
243 reason = "Hm.. Cache lining optimization? Seems unnecessary for a CLI args struct."
244)]
245pub struct GenerateArgs {
246 /// Path to a local rustdoc JSON file.
247 ///
248 /// Generate this file with: `cargo doc --output-format json`
249 /// The JSON file will be in `target/doc/{crate_name}.json`
250 ///
251 /// Mutually exclusive with `--dir`.
252 #[arg(
253 short,
254 long,
255 required_unless_present_any = ["dir"],
256 conflicts_with = "dir"
257 )]
258 pub path: Option<PathBuf>,
259
260 /// Directory containing multiple rustdoc JSON files.
261 ///
262 /// Use this for multi-crate documentation generation. The tool will
263 /// scan the directory for all `*.json` files (rustdoc format) and
264 /// generate documentation for each crate with cross-crate linking.
265 ///
266 /// Generate JSON files with:
267 /// `RUSTDOCFLAGS='-Z unstable-options --output-format json' cargo +nightly doc`
268 ///
269 /// Mutually exclusive with `--path`.
270 #[arg(
271 long,
272 required_unless_present_any = ["path"],
273 conflicts_with = "path"
274 )]
275 pub dir: Option<PathBuf>,
276
277 /// Generate mdBook-compatible SUMMARY.md file.
278 ///
279 /// Only valid with `--dir` for multi-crate documentation.
280 /// Creates a `SUMMARY.md` file in the output directory that can be
281 /// used as the entry point for an mdBook documentation site.
282 #[arg(long, requires = "dir", default_value_t = false)]
283 pub mdbook: bool,
284
285 /// Generate `search_index.json` for client-side search.
286 ///
287 /// Only valid with `--dir` for multi-crate documentation.
288 /// Creates a `search_index.json` file containing all documented items,
289 /// which can be used with client-side search libraries like Fuse.js,
290 /// Lunr.js, or `FlexSearch`.
291 #[arg(long, requires = "dir", default_value_t = false)]
292 pub search_index: bool,
293
294 /// Primary crate name for preferential link resolution.
295 ///
296 /// When specified with `--dir`, links to items in this crate take
297 /// precedence over items with the same name in dependencies.
298 /// This helps resolve ambiguous links like `exit` to the intended
299 /// crate rather than `std::process::exit`.
300 #[arg(long, requires = "dir")]
301 pub primary_crate: Option<String>,
302
303 /// Output directory for generated markdown files.
304 ///
305 /// The directory will be created if it doesn't exist.
306 /// Defaults to `generated_docs/` in the current directory.
307 #[arg(short, long, default_value = "generated_docs")]
308 pub output: PathBuf,
309
310 /// Output format (flat or nested).
311 ///
312 /// - `flat`: All files in one directory
313 /// - `nested`: Directory hierarchy mirroring modules (default)
314 #[arg(short, long, value_enum, default_value_t = CliOutputFormat::Nested)]
315 pub format: CliOutputFormat,
316
317 /// Exclude private (non-public) items from the output.
318 ///
319 /// By default, all items are documented including `pub(crate)`,
320 /// `pub(super)`, and private items. Enable this to only include
321 /// public items.
322 #[arg(long, default_value_t = false)]
323 pub exclude_private: bool,
324
325 /// Include blanket trait implementations in the output.
326 ///
327 /// By default, blanket impls like `From`, `Into`, `TryFrom`, `TryInto`,
328 /// `Any`, `Borrow`, `BorrowMut`, and `ToOwned` are filtered out to reduce
329 /// noise. Enable this to include them in the documentation.
330 #[arg(long, default_value_t = false)]
331 pub include_blanket_impls: bool,
332
333 // === RenderConfig toggles ===
334 /// Minimum number of items before generating a table of contents.
335 ///
336 /// Modules with fewer items than this threshold won't have a TOC.
337 /// Default: 10
338 #[arg(long, default_value_t = 10)]
339 pub toc_threshold: usize,
340
341 /// Disable quick reference tables at the top of modules.
342 #[arg(long, default_value_t = false)]
343 pub no_quick_reference: bool,
344
345 /// Disable grouping impl blocks by category (Derive, Conversion, etc.).
346 #[arg(long, default_value_t = false)]
347 pub no_group_impls: bool,
348
349 /// Hide trivial derive implementations (Clone, Copy, Debug, etc.).
350 #[arg(long, default_value_t = false)]
351 pub hide_trivial_derives: bool,
352
353 /// Disable method-level anchors for deep linking.
354 #[arg(long, default_value_t = false)]
355 pub no_method_anchors: bool,
356
357 /// Include source file locations for items.
358 #[arg(long, default_value_t = false)]
359 pub source_locations: bool,
360
361 /// Include full method documentation instead of first-line summaries.
362 ///
363 /// By default, only the first paragraph of method docs is shown in impl blocks.
364 /// Enable this to include the complete documentation for each method.
365 #[arg(long, default_value_t = false)]
366 pub full_method_docs: bool,
367}
368
369/// Backwards-compatible type alias for existing code.
370pub type Args = GenerateArgs;
371
372/// CLI-compatible output format enum (for clap `ValueEnum` derive).
373#[derive(Clone, Copy, Debug, Default, ValueEnum)]
374pub enum CliOutputFormat {
375 /// Flat structure with double-underscore separators in filenames.
376 #[default]
377 Flat,
378
379 /// Nested directory structure mirroring the module hierarchy.
380 Nested,
381}
382
383impl From<CliOutputFormat> for OutputFormat {
384 fn from(cli: CliOutputFormat) -> Self {
385 match cli {
386 CliOutputFormat::Flat => Self::Flat,
387
388 CliOutputFormat::Nested => Self::Nested,
389 }
390 }
391}