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    /// Additional arguments to pass to cargo doc.
169    ///
170    /// Example: `docs-md docs -- --all-features`
171    #[arg(last = true)]
172    pub cargo_args: Vec<String>,
173}
174
175/// Arguments for the `collect-sources` subcommand.
176#[cfg(feature = "source-parsing")]
177#[derive(Parser, Debug)]
178pub struct CollectSourcesArgs {
179    /// Output directory for collected sources.
180    ///
181    /// If not specified, creates `.source_{timestamp}/` in the workspace root.
182    #[arg(short, long)]
183    pub output: Option<PathBuf>,
184
185    /// Include dev-dependencies in collection.
186    ///
187    /// By default, only regular dependencies are collected.
188    #[arg(long, default_value_t = false)]
189    pub include_dev: bool,
190
191    /// Dry run - show what would be collected without copying.
192    #[arg(long, default_value_t = false)]
193    pub dry_run: bool,
194
195    /// Path to Cargo.toml (defaults to current directory).
196    #[arg(long)]
197    pub manifest_path: Option<PathBuf>,
198}
199
200/// Command-line arguments for direct generation (no subcommand).
201///
202/// The tool accepts input from two mutually exclusive sources:
203/// 1. A local rustdoc JSON file (`--path`)
204/// 2. A directory of rustdoc JSON files (`--dir`)
205#[derive(Parser, Debug, Default)]
206#[expect(
207    clippy::struct_excessive_bools,
208    reason = "Hm.. Cache lining optimization? Seems unnecessary for a CLI args struct."
209)]
210pub struct GenerateArgs {
211    /// Path to a local rustdoc JSON file.
212    ///
213    /// Generate this file with: `cargo doc --output-format json`
214    /// The JSON file will be in `target/doc/{crate_name}.json`
215    ///
216    /// Mutually exclusive with `--dir`.
217    #[arg(
218        short,
219        long,
220        required_unless_present_any = ["dir"],
221        conflicts_with = "dir"
222    )]
223    pub path: Option<PathBuf>,
224
225    /// Directory containing multiple rustdoc JSON files.
226    ///
227    /// Use this for multi-crate documentation generation. The tool will
228    /// scan the directory for all `*.json` files (rustdoc format) and
229    /// generate documentation for each crate with cross-crate linking.
230    ///
231    /// Generate JSON files with:
232    /// `RUSTDOCFLAGS='-Z unstable-options --output-format json' cargo +nightly doc`
233    ///
234    /// Mutually exclusive with `--path`.
235    #[arg(
236        long,
237        required_unless_present_any = ["path"],
238        conflicts_with = "path"
239    )]
240    pub dir: Option<PathBuf>,
241
242    /// Generate mdBook-compatible SUMMARY.md file.
243    ///
244    /// Only valid with `--dir` for multi-crate documentation.
245    /// Creates a `SUMMARY.md` file in the output directory that can be
246    /// used as the entry point for an mdBook documentation site.
247    #[arg(long, requires = "dir", default_value_t = false)]
248    pub mdbook: bool,
249
250    /// Generate `search_index.json` for client-side search.
251    ///
252    /// Only valid with `--dir` for multi-crate documentation.
253    /// Creates a `search_index.json` file containing all documented items,
254    /// which can be used with client-side search libraries like Fuse.js,
255    /// Lunr.js, or `FlexSearch`.
256    #[arg(long, requires = "dir", default_value_t = false)]
257    pub search_index: bool,
258
259    /// Primary crate name for preferential link resolution.
260    ///
261    /// When specified with `--dir`, links to items in this crate take
262    /// precedence over items with the same name in dependencies.
263    /// This helps resolve ambiguous links like `exit` to the intended
264    /// crate rather than `std::process::exit`.
265    #[arg(long, requires = "dir")]
266    pub primary_crate: Option<String>,
267
268    /// Output directory for generated markdown files.
269    ///
270    /// The directory will be created if it doesn't exist.
271    /// Defaults to `generated_docs/` in the current directory.
272    #[arg(short, long, default_value = "generated_docs")]
273    pub output: PathBuf,
274
275    /// Output format (flat or nested).
276    ///
277    /// - `flat`: All files in one directory
278    /// - `nested`: Directory hierarchy mirroring modules (default)
279    #[arg(short, long, value_enum, default_value_t = CliOutputFormat::Nested)]
280    pub format: CliOutputFormat,
281
282    /// Exclude private (non-public) items from the output.
283    ///
284    /// By default, all items are documented including `pub(crate)`,
285    /// `pub(super)`, and private items. Enable this to only include
286    /// public items.
287    #[arg(long, default_value_t = false)]
288    pub exclude_private: bool,
289
290    /// Include blanket trait implementations in the output.
291    ///
292    /// By default, blanket impls like `From`, `Into`, `TryFrom`, `TryInto`,
293    /// `Any`, `Borrow`, `BorrowMut`, and `ToOwned` are filtered out to reduce
294    /// noise. Enable this to include them in the documentation.
295    #[arg(long, default_value_t = false)]
296    pub include_blanket_impls: bool,
297}
298
299/// Backwards-compatible type alias for existing code.
300pub type Args = GenerateArgs;
301
302/// CLI-compatible output format enum (for clap `ValueEnum` derive).
303#[derive(Clone, Copy, Debug, Default, ValueEnum)]
304pub enum CliOutputFormat {
305    /// Flat structure with double-underscore separators in filenames.
306    #[default]
307    Flat,
308
309    /// Nested directory structure mirroring the module hierarchy.
310    Nested,
311}
312
313impl From<CliOutputFormat> for OutputFormat {
314    fn from(cli: CliOutputFormat) -> Self {
315        match cli {
316            CliOutputFormat::Flat => Self::Flat,
317
318            CliOutputFormat::Nested => Self::Nested,
319        }
320    }
321}