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.packages.iter().find(|pkg| pkg.name == package),
119 "Cannot find requested package"
120 ),
121 None => unwrap!(
122 metadata.root_package(),
123 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 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 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 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 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}