1use std::collections::{HashMap, HashSet};
2use std::io;
3use std::path::{Path, PathBuf};
4use std::string::String;
5use std::vec::Vec;
6
7use serde::Deserialize;
8use thiserror::Error;
9
10#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
17pub struct Manifest {
18 pub package: ManifestPackage,
20 pub lib: Option<ManifestLibTarget>,
22 pub bin: Option<Vec<ManifestBinTarget>>,
24 pub features: Option<HashMap<String, HashSet<String>>>,
26 pub dependencies: Option<HashMap<String, ManifestDependency>>,
28 #[serde(rename = "package.metadata.docs.rs")]
30 pub docs_meta: Option<ManifestDocsRsMetadata>,
31}
32
33#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
37pub struct ManifestPackage {
38 pub name: String,
41 pub version: String,
43 pub documentation: Option<String>,
45 pub readme: Option<ManifestReadmePath>,
47 pub repository: Option<String>,
49}
50
51#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
55pub struct ManifestLibTarget {
56 pub name: Option<String>,
58 pub path: Option<String>,
60 pub doc: Option<bool>,
62}
63
64#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
68pub struct ManifestBinTarget {
69 pub name: String,
71 pub path: Option<String>,
73 pub doc: Option<bool>,
75}
76
77#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
81#[serde(untagged)]
82pub enum ManifestDependency {
83 Version(String),
85 Details(ManifestDependencyDetails),
88}
89
90#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Deserialize)]
94pub struct ManifestDependencyDetails {
95 pub optional: Option<bool>,
97}
98
99#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
103pub struct ManifestDocsRsMetadata {
104 pub features: Option<HashSet<String>>,
106 #[serde(rename = "all-features")]
108 pub all_features: Option<bool>,
109 #[serde(rename = "no-default-features")]
111 pub no_default_features: Option<bool>,
112 #[serde(rename = "default-target")]
114 pub default_target: Option<String>,
115 pub targets: Option<Vec<String>>,
117}
118
119#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
126#[serde(untagged)]
127pub enum ManifestReadmePath {
128 Path(PathBuf),
130 Bool(bool),
133}
134
135impl Manifest {
136 pub fn from_name_and_version(name: String, version: String) -> Self {
138 Manifest {
139 package: ManifestPackage {
140 name,
141 version,
142 repository: None,
143 documentation: None,
144 readme: None,
145 },
146 lib: None,
147 bin: None,
148 features: None,
149 dependencies: None,
150 docs_meta: None,
151 }
152 }
153
154 pub fn from_cargo_toml_content(content: &str) -> Result<Self, TomlParseError> {
156 Ok(toml::from_str(content)?)
157 }
158
159 pub fn from_cargo_toml_path(path: &Path) -> Result<Self, TomlReadError> {
161 let content = std::fs::read_to_string(path).map_err(|err| TomlReadError::IoError {
162 path: path.to_path_buf(),
163 err,
164 })?;
165 Self::from_cargo_toml_content(&content).map_err(|err| TomlReadError::ParseError {
166 path: path.to_path_buf(),
167 err,
168 })
169 }
170
171 pub fn from_package_path(path: &Path) -> Result<Self, TomlReadError> {
173 Self::from_cargo_toml_path(&path.join("Cargo.toml"))
174 }
175
176 pub fn relative_readme_path(&self, root: &Path) -> Option<&Path> {
178 match &self.package.readme {
179 Some(value) => match value {
180 ManifestReadmePath::Bool(false) => None,
181 ManifestReadmePath::Bool(true) => Some(Path::new("README.md")),
182 ManifestReadmePath::Path(value) => Some(value),
183 },
184 None => Manifest::default_readme_filename(root),
185 }
186 }
187
188 pub fn default_readme_filename(root: &Path) -> Option<&'static Path> {
190 const DEFAULT_FILES: [&str; 3] = ["README.md", "README.txt", "README"];
191
192 for &filename in DEFAULT_FILES.iter() {
193 if root.join(filename).is_file() {
194 return Some(Path::new(filename));
195 }
196 }
197
198 None
199 }
200
201 pub fn is_lib_documented_by_default(&self) -> bool {
205 self.lib.as_ref().and_then(|lib| lib.doc).unwrap_or(true)
206 }
207
208 pub fn relative_lib_path(&self) -> &Path {
212 Path::new(
213 self.lib
214 .as_ref()
215 .and_then(|lib| lib.path.as_deref())
216 .unwrap_or("src/lib.rs"),
217 )
218 }
219
220 pub fn default_relative_bin_path(&self) -> &'static Path {
224 Path::new("src/main.rs")
225 }
226
227 pub fn relative_bin_path(&self, name: &str) -> Result<PathBuf, BinPathError> {
231 use std::string::ToString;
232
233 let mut bins = self.bin.iter().flatten().filter(|bin| bin.name == name);
234 match (bins.next(), bins.next()) {
235 (Some(_), Some(_)) => Err(BinPathError::SpecifiedMoreThanOnce(name.to_string())),
236 (Some(bin), None) => Ok(bin.path.as_ref().map_or_else(
237 || PathBuf::from("src/bin").join(Path::new(&bin.name)),
238 PathBuf::from,
239 )),
240 (None, None) => {
241 if name == self.package.name {
242 Ok(PathBuf::from("src/main.rs"))
243 } else {
244 Err(BinPathError::NotFound(name.to_string()))
245 }
246 }
247 (None, Some(_)) => unreachable!(),
248 }
249 }
250
251 pub fn default_relative_target_path(&self) -> &Path {
255 if self.is_lib_documented_by_default() {
256 self.relative_lib_path()
257 } else {
258 self.default_relative_bin_path()
259 }
260 }
261
262 pub fn docs_rs_default_target(&self) -> &str {
266 const DEFAULT_TARGET: &str = "x86_64-unknown-linux-gnu";
267
268 if let Some(docs_meta) = &self.docs_meta {
269 if let Some(default_target) = &docs_meta.default_target {
270 return default_target;
271 }
272 if let Some(targets) = &docs_meta.targets {
273 if let Some(first_target) = targets.first() {
274 return first_target;
275 }
276 }
277 }
278 DEFAULT_TARGET
279 }
280
281 pub fn default_features(&self) -> HashSet<&str> {
283 use core::ops::Deref;
284
285 if let Some(features) = self.features.as_ref() {
286 if let Some(default_features) = features.get("default") {
287 return default_features.iter().map(Deref::deref).collect();
288 }
289 }
290 HashSet::new()
291 }
292
293 pub fn all_features(&self) -> HashSet<&str> {
295 use core::ops::Deref;
296
297 let mut all_features = HashSet::new();
298 if let Some(features) = self.features.as_ref() {
299 all_features.extend(features.keys().map(Deref::deref));
300 }
301 if let Some(dependencies) = self.dependencies.as_ref() {
302 all_features.extend(dependencies.iter().filter_map(|(key, dep)| match dep {
303 ManifestDependency::Details(ManifestDependencyDetails {
304 optional: Some(true),
305 }) => Some(key.deref()),
306 _ => None,
307 }));
308 }
309 all_features
310 }
311
312 pub fn docs_rs_features(&self) -> HashSet<&str> {
316 use core::ops::Deref;
317
318 let all_features = self
319 .docs_meta
320 .as_ref()
321 .and_then(|docs_meta| docs_meta.all_features)
322 .unwrap_or(false);
323 if all_features {
324 return self.all_features();
325 }
326
327 let no_default_features = self
328 .docs_meta
329 .as_ref()
330 .and_then(|docs_meta| docs_meta.no_default_features)
331 .unwrap_or(false);
332 let features = self
333 .docs_meta
334 .as_ref()
335 .and_then(|docs_meta| docs_meta.features.as_ref())
336 .map(|features| features.iter().map(Deref::deref).collect());
337
338 match (no_default_features, features) {
339 (true, Some(features)) => features,
340 (true, None) => HashSet::new(),
341 (false, Some(features)) => features.union(&self.default_features()).copied().collect(),
342 (false, None) => self.default_features(),
343 }
344 }
345}
346
347#[derive(Clone, Debug, Eq, Error, PartialEq)]
349pub enum TomlParseError {
350 #[error(transparent)]
352 ParseError(#[from] toml::de::Error),
353}
354
355#[derive(Debug, Error)]
357pub enum TomlReadError {
358 #[error("Failed to read toml at `{path}`: {err}")]
360 IoError {
361 path: PathBuf,
363 err: io::Error,
365 },
366 #[error("Failed to parse toml at `{path}`: {err}")]
368 ParseError {
369 path: PathBuf,
371 err: TomlParseError,
373 },
374}
375
376#[derive(Clone, Debug, Eq, Error, PartialEq)]
378pub enum BinPathError {
379 #[error("Binary `{0}` not found.")]
381 NotFound(String),
382 #[error("Binary `{0}` specified more than once.")]
384 SpecifiedMoreThanOnce(String),
385}