cargo_doc2readme/
lib.rs

1//! **THIS IS NOT A LIBRARY. NONE OF THE APIS ARE PUBLIC. THEY DON'T
2//! ADHERE TO SEMVER. DON'T EVEN USE AT YOUR OWN RISK. DON'T USE IT
3//! AT ALL.**
4
5use cargo_metadata::{CargoOpt, MetadataCommand, Target};
6use log::{debug, info};
7use semver::Version;
8use std::{borrow::Cow, collections::HashMap, env, fmt::Display, fs, path::PathBuf};
9
10#[doc(hidden)]
11pub mod depinfo;
12#[doc(hidden)]
13pub mod diagnostic;
14#[doc(hidden)]
15pub mod input;
16#[doc(hidden)]
17pub mod links;
18#[doc(hidden)]
19pub mod output;
20#[doc(hidden)]
21pub mod preproc;
22#[doc(hidden)]
23pub mod verify;
24
25use crate::input::Scope;
26use diagnostic::Diagnostic;
27use input::{CrateCode, InputFile, TargetType};
28
29#[doc(hidden)]
30#[allow(clippy::too_many_arguments)] // TODO
31/// Read input. The manifest path options, if present, will be passed to
32/// `cargo metadata`. If you set expand_macros to true, the input will be passed to the
33/// rust compiler to expand macros. This will only work on a nightly compiler. The
34/// template doesn't have to exist, a default will be used if it does not exist.
35pub fn read_input(
36	manifest_path: Option<PathBuf>,
37	package: Option<String>,
38	prefer_bin: bool,
39	expand_macros: bool,
40	template: PathBuf,
41	features: Option<String>,
42	no_default_features: bool,
43	all_features: bool
44) -> (InputFile, Cow<'static, str>, Diagnostic) {
45	/// Create a fake input when reading the input failed before we had any code.
46	fn fail<T: Display>(msg: T) -> (InputFile, Cow<'static, str>, Diagnostic) {
47		let input = InputFile {
48			crate_name: "N/A".into(),
49			crate_version: Version::new(0, 0, 0),
50			target_type: TargetType::Lib,
51			repository: None,
52			license: None,
53			rust_version: None,
54			rustdoc: String::new(),
55			dependencies: HashMap::new(),
56			scope: Scope::empty()
57		};
58		let template = "".into();
59		let mut diagnostic = Diagnostic::new("<none>".into(), String::new());
60		diagnostic.error(msg);
61		(input, template, diagnostic)
62	}
63
64	trait Fail {
65		type Ok;
66
67		fn fail(self, msg: &'static str) -> Result<Self::Ok, Cow<'static, str>>;
68	}
69
70	impl<T> Fail for Option<T> {
71		type Ok = T;
72
73		fn fail(self, msg: &'static str) -> Result<Self::Ok, Cow<'static, str>> {
74			self.ok_or(Cow::Borrowed(msg))
75		}
76	}
77
78	impl<T, E: Display> Fail for Result<T, E> {
79		type Ok = T;
80
81		fn fail(self, msg: &'static str) -> Result<T, Cow<'static, str>> {
82			self.map_err(|err| format!("{msg}: {err}").into())
83		}
84	}
85
86	macro_rules! unwrap {
87		($expr:expr) => {
88			match $expr {
89				Ok(ok) => ok,
90				Err(err) => return fail(err)
91			}
92		};
93
94		($expr:expr, $msg:literal) => {
95			match Fail::fail($expr, $msg) {
96				Ok(ok) => ok,
97				Err(err) => return fail(err)
98			}
99		};
100	}
101
102	// get the cargo manifest path
103	let manifest_path = match manifest_path {
104		Some(path) if path.is_relative() => Some(env::current_dir().unwrap().join(path)),
105		Some(path) => Some(path),
106		None => None
107	};
108
109	// parse the cargo metadata
110	let mut cmd = MetadataCommand::new();
111	cmd.features(CargoOpt::AllFeatures);
112	if let Some(path) = &manifest_path {
113		cmd.manifest_path(path);
114	}
115	let metadata = unwrap!(cmd.exec(), "Failed to get cargo metadata");
116	let pkg = match package.as_deref() {
117		Some(package) => unwrap!(
118			metadata.packages.iter().find(|pkg| pkg.name == package),
119			"Cannot find requested package"
120		),
121		None => unwrap!(
122			metadata.root_package(),
123			// TODO this could be a real "help" message from ariadne
124			r#"Missing package. Please make sure there is a package here, workspace roots don't contain any documentation.
125
126Help: You can use --manifest-path and/or -p to specify the package to use."#
127		)
128	};
129
130	// find the target whose rustdoc comment we'll use.
131	// this uses a library target if exists, otherwise a binary target with the same name as the
132	// package, or otherwise the first binary target
133	let is_lib = |target: &&Target| target.is_lib() || target.is_proc_macro();
134	let is_default_bin =
135		|target: &&Target| target.is_bin() && target.name == pkg.name.as_str();
136	let target_and_type = if prefer_bin {
137		pkg.targets
138			.iter()
139			.find(is_default_bin)
140			.map(|target| (target, TargetType::Bin))
141			.or_else(|| {
142				pkg.targets
143					.iter()
144					.find(is_lib)
145					.map(|target| (target, TargetType::Lib))
146			})
147	} else {
148		pkg.targets
149			.iter()
150			.find(is_lib)
151			.map(|target| (target, TargetType::Lib))
152			.or_else(|| {
153				pkg.targets
154					.iter()
155					.find(is_default_bin)
156					.map(|target| (target, TargetType::Bin))
157			})
158	};
159	let (target, target_type) = unwrap!(
160		target_and_type.or_else(|| {
161			pkg.targets
162				.iter()
163				.find(|target| target.is_bin())
164				.map(|target| (target, TargetType::Bin))
165		}),
166		"Failed to find a library or binary target"
167	);
168
169	// resolve the template
170	let template: Cow<'static, str> = if template.exists() {
171		unwrap!(fs::read_to_string(template), "Failed to read template").into()
172	} else {
173		include_str!("README.j2").into()
174	};
175
176	// read crate code
177	let file = target.src_path.as_std_path();
178	let filename = file
179		.file_name()
180		.expect("File has no filename")
181		.to_string_lossy()
182		.into_owned();
183	let code = if expand_macros {
184		unwrap!(
185			CrateCode::read_expansion(
186				manifest_path.as_ref(),
187				package,
188				target,
189				features,
190				no_default_features,
191				all_features
192			),
193			"Failed to read crate code"
194		)
195	} else {
196		unwrap!(CrateCode::read_from_disk(file), "Failed to read crate code")
197	};
198	let mut diagnostics = Diagnostic::new(filename, code.0.clone());
199
200	// process the target
201	info!("Reading {}", file.display());
202	let input_file =
203		input::read_code(&metadata, pkg, code, target_type, &mut diagnostics);
204	debug!("Processing {input_file:#?}");
205
206	(input_file, template, diagnostics)
207}