use std::collections::{HashMap, HashSet};
#[cfg(all(feature = "toml", feature = "thiserror"))]
use std::io;
use std::path::{Path, PathBuf};
use std::string::String;
use std::vec::Vec;
#[cfg(feature = "serde")]
use serde::Deserialize;
#[cfg(all(feature = "toml", feature = "thiserror"))]
use thiserror::Error;
#[cfg_attr(feature = "serde", derive(Deserialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Manifest {
pub package: ManifestPackage,
pub lib: Option<ManifestLibTarget>,
pub bin: Option<Vec<ManifestBinTarget>>,
pub features: Option<HashMap<String, HashSet<String>>>,
pub dependencies: Option<HashMap<String, ManifestDependency>>,
#[cfg_attr(feature = "serde", serde(rename = "package.metadata.docs.rs"))]
pub docs_meta: Option<ManifestDocsRsMetadata>,
}
#[cfg_attr(feature = "serde", derive(Deserialize))]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ManifestPackage {
pub name: String,
pub version: String,
pub documentation: Option<String>,
pub readme: Option<ManifestReadmePath>,
pub repository: Option<String>,
}
#[cfg_attr(feature = "serde", derive(Deserialize))]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ManifestLibTarget {
pub name: Option<String>,
pub path: Option<String>,
pub doc: Option<bool>,
}
#[cfg_attr(feature = "serde", derive(Deserialize))]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ManifestBinTarget {
pub name: String,
pub path: Option<String>,
pub doc: Option<bool>,
}
#[cfg_attr(feature = "serde", derive(Deserialize))]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ManifestDependency {
pub optional: Option<bool>,
}
#[cfg_attr(feature = "serde", derive(Deserialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ManifestDocsRsMetadata {
pub features: Option<HashSet<String>>,
#[cfg_attr(feature = "serde", serde(rename = "all-features"))]
pub all_features: Option<bool>,
#[cfg_attr(feature = "serde", serde(rename = "no-default-features"))]
pub no_default_features: Option<bool>,
#[cfg_attr(feature = "serde", serde(rename = "default-target"))]
pub default_target: Option<String>,
pub targets: Option<Vec<String>>,
}
#[cfg_attr(feature = "serde", derive(Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ManifestReadmePath {
Path(PathBuf),
Bool(bool),
}
impl Manifest {
pub fn from_name_and_version(name: String, version: String) -> Self {
Manifest {
package: ManifestPackage {
name,
version,
repository: None,
documentation: None,
readme: None,
},
lib: None,
bin: None,
features: None,
dependencies: None,
docs_meta: None,
}
}
#[cfg(all(feature = "toml", feature = "serde", feature = "thiserror"))]
pub fn from_cargo_toml_content(content: &str) -> Result<Self, TomlParseError> {
Ok(toml::from_str(&content)?)
}
#[cfg(all(feature = "toml", feature = "serde", feature = "thiserror"))]
pub fn from_cargo_toml_path(path: &Path) -> Result<Self, TomlReadError> {
let content = std::fs::read_to_string(path).map_err(|err| TomlReadError::IoError {
path: path.to_path_buf(),
err,
})?;
Self::from_cargo_toml_content(&content).map_err(|err| TomlReadError::ParseError {
path: path.to_path_buf(),
err,
})
}
#[cfg(all(feature = "toml", feature = "serde", feature = "thiserror"))]
pub fn from_package_path(path: &Path) -> Result<Self, TomlReadError> {
Self::from_cargo_toml_path(&path.join("Cargo.toml"))
}
pub fn relative_readme_path(&self, root: &Path) -> Option<&Path> {
match &self.package.readme {
Some(value) => match value {
ManifestReadmePath::Bool(false) => None,
ManifestReadmePath::Bool(true) => Some(Path::new("README.md")),
ManifestReadmePath::Path(value) => Some(value),
},
None => Manifest::default_readme_filename(root),
}
}
pub fn default_readme_filename(root: &Path) -> Option<&'static Path> {
const DEFAULT_FILES: [&str; 3] = ["README.md", "README.txt", "README"];
for &filename in DEFAULT_FILES.iter() {
if root.join(filename).is_file() {
return Some(Path::new(filename));
}
}
None
}
pub fn is_lib_documented_by_default(&self) -> bool {
self.lib.as_ref().and_then(|lib| lib.doc).unwrap_or(true)
}
pub fn relative_lib_path(&self) -> &Path {
Path::new(
self.lib
.as_ref()
.and_then(|lib| lib.path.as_deref())
.unwrap_or("src/lib.rs"),
)
}
pub fn default_relative_bin_path(&self) -> &'static Path {
Path::new("src/main.rs")
}
#[cfg(all(feature = "toml", feature = "thiserror"))]
pub fn relative_bin_path(&self, name: &str) -> Result<PathBuf, BinPathError> {
use std::string::ToString;
let mut bins = self.bin.iter().flatten().filter(|bin| bin.name == name);
match (bins.next(), bins.next()) {
(Some(_), Some(_)) => Err(BinPathError::SpecifiedMoreThanOnce(name.to_string())),
(Some(bin), None) => Ok(bin.path.as_ref().map_or_else(
|| PathBuf::from("src/bin").join(Path::new(&bin.name)),
PathBuf::from,
)),
(None, None) => {
if name == self.package.name {
Ok(PathBuf::from("src/main.rs"))
} else {
Err(BinPathError::NotFound(name.to_string()))
}
}
(None, Some(_)) => unreachable!(),
}
}
pub fn default_relative_target_path(&self) -> &Path {
if self.is_lib_documented_by_default() {
self.relative_lib_path()
} else {
self.default_relative_bin_path()
}
}
pub fn docs_rs_default_target(&self) -> &str {
const DEFAULT_TARGET: &str = "x86_64-unknown-linux-gnu";
if let Some(docs_meta) = &self.docs_meta {
if let Some(default_target) = &docs_meta.default_target {
return default_target;
}
if let Some(targets) = &docs_meta.targets {
if let Some(first_target) = targets.first() {
return first_target;
}
}
}
DEFAULT_TARGET
}
pub fn default_features(&self) -> HashSet<&str> {
use core::ops::Deref;
if let Some(features) = self.features.as_ref() {
if let Some(default_features) = features.get("default") {
return default_features.iter().map(Deref::deref).collect();
}
}
HashSet::new()
}
pub fn all_features(&self) -> HashSet<&str> {
use core::ops::Deref;
let mut all_features = HashSet::new();
if let Some(features) = self.features.as_ref() {
all_features.extend(features.keys().map(Deref::deref));
}
if let Some(dependencies) = self.dependencies.as_ref() {
all_features.extend(
dependencies
.iter()
.filter_map(|(name, dep)| match dep.optional {
Some(true) => Some(name.deref()),
_ => None,
}),
);
}
all_features
}
pub fn docs_rs_features(&self) -> HashSet<&str> {
use core::ops::Deref;
let all_features = self
.docs_meta
.as_ref()
.and_then(|docs_meta| docs_meta.all_features)
.unwrap_or(false);
if all_features {
return self.all_features();
}
let no_default_features = self
.docs_meta
.as_ref()
.and_then(|docs_meta| docs_meta.no_default_features)
.unwrap_or(false);
let features = self
.docs_meta
.as_ref()
.and_then(|docs_meta| docs_meta.features.as_ref())
.map(|features| features.iter().map(Deref::deref).collect());
match (no_default_features, features) {
(true, Some(features)) => features,
(true, None) => HashSet::new(),
(false, Some(features)) => features.union(&self.default_features()).copied().collect(),
(false, None) => self.default_features(),
}
}
}
#[cfg(all(feature = "toml", feature = "thiserror"))]
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum TomlParseError {
#[error(transparent)]
ParseError(#[from] toml::de::Error),
}
#[cfg(all(feature = "toml", feature = "thiserror"))]
#[derive(Debug, Error)]
pub enum TomlReadError {
#[error("Failed to read toml at `{path}`: {err}")]
IoError {
path: PathBuf,
err: io::Error,
},
#[error("Failed to parse toml at `{path}`: {err}")]
ParseError {
path: PathBuf,
err: TomlParseError,
},
}
#[cfg(all(feature = "toml", feature = "thiserror"))]
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum BinPathError {
#[error("Binary `{0}` not found.")]
NotFound(String),
#[error("Binary `{0}` specified more than once.")]
SpecifiedMoreThanOnce(String),
}