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}