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}