1use 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)] pub 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 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 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 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
119 .packages
120 .iter()
121 .find(|pkg| pkg.name.as_str() == package),
122 "Cannot find requested package"
123 ),
124 None => unwrap!(
125 metadata.root_package(),
126 r#"Missing package. Please make sure there is a package here, workspace roots don't contain any documentation.
128
129Help: You can use --manifest-path and/or -p to specify the package to use."#
130 )
131 };
132
133 let is_lib = |target: &&Target| target.is_lib() || target.is_proc_macro();
137 let is_default_bin =
138 |target: &&Target| target.is_bin() && target.name == pkg.name.as_str();
139 let target_and_type = if prefer_bin {
140 pkg.targets
141 .iter()
142 .find(is_default_bin)
143 .map(|target| (target, TargetType::Bin))
144 .or_else(|| {
145 pkg.targets
146 .iter()
147 .find(is_lib)
148 .map(|target| (target, TargetType::Lib))
149 })
150 } else {
151 pkg.targets
152 .iter()
153 .find(is_lib)
154 .map(|target| (target, TargetType::Lib))
155 .or_else(|| {
156 pkg.targets
157 .iter()
158 .find(is_default_bin)
159 .map(|target| (target, TargetType::Bin))
160 })
161 };
162 let (target, target_type) = unwrap!(
163 target_and_type.or_else(|| {
164 pkg.targets
165 .iter()
166 .find(|target| target.is_bin())
167 .map(|target| (target, TargetType::Bin))
168 }),
169 "Failed to find a library or binary target"
170 );
171
172 let template: Cow<'static, str> = if template.exists() {
174 unwrap!(fs::read_to_string(template), "Failed to read template").into()
175 } else {
176 include_str!("README.j2").into()
177 };
178
179 let file = target.src_path.as_std_path();
181 let filename = file
182 .file_name()
183 .expect("File has no filename")
184 .to_string_lossy()
185 .into_owned();
186 let code = if expand_macros {
187 unwrap!(
188 CrateCode::read_expansion(
189 manifest_path.as_ref(),
190 package,
191 target,
192 features,
193 no_default_features,
194 all_features
195 ),
196 "Failed to read crate code"
197 )
198 } else {
199 unwrap!(CrateCode::read_from_disk(file), "Failed to read crate code")
200 };
201 let mut diagnostics = Diagnostic::new(filename, code.0.clone());
202
203 info!("Reading {}", file.display());
205 let input_file =
206 input::read_code(&metadata, pkg, code, target_type, &mut diagnostics);
207 debug!("Processing {input_file:#?}");
208
209 (input_file, template, diagnostics)
210}