#![allow(clippy::large_enum_variant)]
use serde::Deserializer;
use serde::{Deserialize, Serialize, Serializer};
use std::collections::BTreeMap;
use std::fs;
use std::io;
use std::path::Path;
pub use toml::Value;
pub type DepsSet = BTreeMap<String, Dependency>;
pub type TargetDepsSet = BTreeMap<String, Target>;
pub type FeatureSet = BTreeMap<String, Vec<String>>;
pub type PatchSet = BTreeMap<String, DepsSet>;
mod afs;
mod error;
pub use crate::afs::*;
pub use crate::error::Error;
use serde::de::{Error as _, Unexpected};
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Manifest<Metadata = Value> {
#[serde(skip_serializing_if = "Option::is_none")]
pub package: Option<Package<Metadata>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace: Option<Workspace>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_optional_tables_last"
)]
pub dependencies: Option<DepsSet>,
#[serde(
skip_serializing_if = "Option::is_none",
alias = "dev_dependencies",
serialize_with = "serialize_optional_tables_last"
)]
pub dev_dependencies: Option<DepsSet>,
#[serde(
skip_serializing_if = "Option::is_none",
alias = "build_dependencies",
serialize_with = "serialize_optional_tables_last"
)]
pub build_dependencies: Option<DepsSet>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<TargetDepsSet>,
#[serde(skip_serializing_if = "Option::is_none")]
pub features: Option<FeatureSet>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bin: Option<Vec<Product>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bench: Option<Vec<Product>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub test: Option<Vec<Product>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub example: Option<Vec<Product>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub patch: Option<PatchSet>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lib: Option<Product>,
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<Profiles>,
#[serde(skip_serializing_if = "Option::is_none")]
pub badges: Option<Badges>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub struct Workspace {
#[serde(default)]
pub members: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none", alias = "default_members")]
pub default_members: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resolver: Option<Resolver>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dependencies: Option<DepsSet>,
#[serde(skip_serializing_if = "Option::is_none")]
pub package: Option<WorkspacePackage>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub struct WorkspacePackage {
#[serde(skip_serializing_if = "Option::is_none")]
pub edition: Option<Edition>,
pub version: Option<String>,
pub authors: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub homepage: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub documentation: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub readme: Option<StringOrBool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keywords: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub categories: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub license: Option<String>,
#[serde(rename = "license-file")]
#[serde(skip_serializing_if = "Option::is_none")]
pub license_file: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub publish: Option<Publish>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<Vec<String>>,
#[serde(rename = "rust-version")]
pub rust_version: Option<String>,
}
fn default_true() -> bool {
true
}
impl Manifest<Value> {
pub fn from_slice(cargo_toml_content: &[u8]) -> Result<Self, Error> {
Self::from_slice_with_metadata(cargo_toml_content)
}
pub fn from_path(cargo_toml_path: impl AsRef<Path>) -> Result<Self, Error> {
Self::from_path_with_metadata(cargo_toml_path)
}
}
impl FromStr for Manifest<Value> {
type Err = Error;
fn from_str(cargo_toml_content: &str) -> Result<Self, Self::Err> {
Self::from_slice_with_metadata(cargo_toml_content.as_bytes())
}
}
impl<Metadata: for<'a> Deserialize<'a>> Manifest<Metadata> {
pub fn from_slice_with_metadata(cargo_toml_content: &[u8]) -> Result<Self, Error> {
let mut manifest: Self = toml::from_slice(cargo_toml_content)?;
if manifest.package.is_none() && manifest.workspace.is_none() {
let val: Value = toml::from_slice(cargo_toml_content)?;
if let Some(project) = val.get("project") {
manifest.package = Some(project.clone().try_into()?);
} else {
manifest.package = Some(val.try_into()?);
}
}
Ok(manifest)
}
pub fn from_path_with_metadata(cargo_toml_path: impl AsRef<Path>) -> Result<Self, Error> {
let cargo_toml_path = cargo_toml_path.as_ref();
let cargo_toml_content = fs::read(cargo_toml_path)?;
let mut manifest = Self::from_slice_with_metadata(&cargo_toml_content)?;
manifest.complete_from_path(cargo_toml_path)?;
Ok(manifest)
}
pub fn complete_from_path(&mut self, path: &Path) -> Result<(), Error> {
let manifest_dir = path
.parent()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "bad path"))?;
self.complete_from_abstract_filesystem(Filesystem::new(manifest_dir))
}
pub fn complete_from_abstract_filesystem(
&mut self,
fs: impl AbstractFilesystem,
) -> Result<(), Error> {
if let Some(ref mut package) = self.package {
let src = match fs.file_names_in("src") {
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(Default::default()),
result => result,
}?;
let edition = match package.edition {
Some(MaybeInherited::Local(edition)) => Some(edition),
_ => None,
};
if let Some(ref mut lib) = self.lib {
lib.required_features.clear(); } else if src.contains("lib.rs") {
self.lib = Some(Product {
name: Some(package.name.replace('-', "_")),
path: Some("src/lib.rs".to_string()),
edition,
crate_type: Some(vec!["rlib".to_string()]),
..Product::default()
})
}
if package.autobins && self.bin.is_none() {
let mut bin = autoset(package, "src/bin", &fs);
if src.contains("main.rs") {
bin.push(Product {
name: Some(package.name.clone()),
path: Some("src/main.rs".to_string()),
edition,
..Product::default()
})
}
self.bin = Some(bin);
}
if package.autoexamples && self.example.is_none() {
self.example = Some(autoset(package, "examples", &fs));
}
if package.autotests && self.test.is_none() {
self.test = Some(autoset(package, "tests", &fs));
}
if package.autobenches && self.bench.is_none() {
self.bench = Some(autoset(package, "benches", &fs));
}
if package.build.is_none()
&& fs
.file_names_in(".")
.map_or(false, |dir| dir.contains("build.rs"))
{
package.build = Some(Value::String("build.rs".to_string()));
}
}
Ok(())
}
}
fn autoset<T>(package: &Package<T>, dir: &str, fs: &dyn AbstractFilesystem) -> Vec<Product> {
let mut out = Vec::new();
let edition = match package.edition {
Some(MaybeInherited::Local(edition)) => Some(edition),
_ => None,
};
if let Ok(bins) = fs.file_names_in(dir) {
for name in bins {
let rel_path = format!("{}/{}", dir, name);
if name.ends_with(".rs") {
out.push(Product {
name: Some(name.trim_end_matches(".rs").into()),
path: Some(rel_path),
edition,
..Product::default()
})
} else if let Ok(sub) = fs.file_names_in(&rel_path) {
if sub.contains("main.rs") {
out.push(Product {
name: Some(name.into()),
path: Some(rel_path + "/main.rs"),
edition,
..Product::default()
})
}
}
}
}
out
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Profiles {
pub release: Option<Profile>,
pub dev: Option<Profile>,
pub test: Option<Profile>,
pub bench: Option<Profile>,
pub doc: Option<Profile>,
#[serde(flatten)]
pub custom: BTreeMap<String, Profile>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Profile {
#[serde(alias = "opt_level")]
pub opt_level: Option<Value>,
pub debug: Option<Value>,
pub rpath: Option<bool>,
pub inherits: Option<String>,
pub lto: Option<Value>,
#[serde(alias = "debug_assertions")]
pub debug_assertions: Option<bool>,
#[serde(alias = "codegen_units")]
pub codegen_units: Option<u16>,
pub panic: Option<String>,
pub incremental: Option<bool>,
#[serde(alias = "overflow_checks")]
pub overflow_checks: Option<bool>,
#[serde(default)]
pub package: BTreeMap<String, Value>,
pub build_override: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Product {
pub path: Option<String>,
pub name: Option<String>,
#[serde(default = "default_true")]
pub test: bool,
#[serde(default = "default_true")]
pub doctest: bool,
#[serde(default = "default_true")]
pub bench: bool,
#[serde(default = "default_true")]
pub doc: bool,
#[serde(default)]
pub plugin: bool,
#[serde(default, alias = "proc_macro")]
pub proc_macro: bool,
#[serde(default = "default_true")]
pub harness: bool,
#[serde(default)]
pub edition: Option<Edition>,
#[serde(default, alias = "required_features")]
pub required_features: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none", alias = "crate_type")]
pub crate_type: Option<Vec<String>>,
}
impl Default for Product {
fn default() -> Self {
Self {
path: None,
name: None,
test: true,
doctest: true,
bench: true,
doc: true,
harness: true,
plugin: false,
proc_macro: false,
required_features: Vec::new(),
crate_type: None,
edition: Some(Edition::default()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Target {
#[serde(default)]
pub dependencies: DepsSet,
#[serde(default, alias = "dev_dependencies")]
pub dev_dependencies: DepsSet,
#[serde(default, alias = "build_dependencies")]
pub build_dependencies: DepsSet,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Dependency {
Simple(String),
Detailed(DependencyDetail),
}
impl Dependency {
pub fn detail(&self) -> Option<&DependencyDetail> {
match *self {
Dependency::Simple(_) => None,
Dependency::Detailed(ref d) => Some(d),
}
}
pub fn req(&self) -> &str {
match *self {
Dependency::Simple(ref v) => v,
Dependency::Detailed(ref d) => d.version.as_deref().unwrap_or("*"),
}
}
pub fn req_features(&self) -> &[String] {
match *self {
Dependency::Simple(_) => &[],
Dependency::Detailed(ref d) => d.features.as_deref().unwrap_or(&[]),
}
}
pub fn optional(&self) -> bool {
self.detail().map_or(false, |d| d.optional.unwrap_or(false))
}
pub fn package(&self) -> Option<&str> {
match *self {
Dependency::Simple(_) => None,
Dependency::Detailed(ref d) => d.package.as_deref(),
}
}
pub fn git(&self) -> Option<&str> {
self.detail().and_then(|d| d.git.as_deref())
}
pub fn is_crates_io(&self) -> bool {
match *self {
Dependency::Simple(_) => true,
Dependency::Detailed(ref d) => {
d.path.is_none()
&& d.registry.is_none()
&& d.registry_index.is_none()
&& d.git.is_none()
&& d.tag.is_none()
&& d.branch.is_none()
&& d.rev.is_none()
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DependencyDetail {
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub registry: Option<String>,
#[serde(alias = "registry_index")]
pub registry_index: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub git: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub branch: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rev: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub features: Option<Vec<String>>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub optional: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace: Option<bool>,
#[serde(default, alias = "default_features")]
#[serde(skip_serializing_if = "Option::is_none")]
pub default_features: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub package: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq)]
#[serde(untagged)]
pub enum MaybeInherited<T> {
Inherited { workspace: True },
Local(T),
}
impl<T> MaybeInherited<T> {
pub fn inherited() -> Self {
Self::Inherited { workspace: True }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[doc(hidden)]
pub struct True;
impl Serialize for True {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bool(true)
}
}
impl<'de> Deserialize<'de> for True {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if bool::deserialize(deserializer)? {
Ok(Self)
} else {
Err(D::Error::invalid_value(
Unexpected::Bool(false),
&"a `true` boolean value",
))
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Package<Metadata = Value> {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub edition: Option<MaybeInherited<Edition>>,
pub version: MaybeInherited<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub build: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub authors: Option<MaybeInherited<Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<MaybeInherited<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub homepage: Option<MaybeInherited<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub documentation: Option<MaybeInherited<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub readme: Option<MaybeInherited<StringOrBool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keywords: Option<MaybeInherited<Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub categories: Option<MaybeInherited<Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub license: Option<MaybeInherited<String>>,
#[serde(rename = "license-file")]
#[serde(skip_serializing_if = "Option::is_none")]
pub license_file: Option<MaybeInherited<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub repository: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(rename = "rust-version")]
pub rust_version: Option<MaybeInherited<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude: Option<MaybeInherited<Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<MaybeInherited<Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_run: Option<String>,
#[serde(default = "default_true")]
pub autobins: bool,
#[serde(default = "default_true")]
pub autoexamples: bool,
#[serde(default = "default_true")]
pub autotests: bool,
#[serde(default = "default_true")]
pub autobenches: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub publish: Option<MaybeInherited<Publish>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resolver: Option<Resolver>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum StringOrBool {
String(String),
Bool(bool),
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Publish {
Flag(bool),
Registry(Vec<String>),
}
impl Default for Publish {
fn default() -> Self {
Publish::Flag(true)
}
}
impl PartialEq<Publish> for bool {
fn eq(&self, p: &Publish) -> bool {
match *p {
Publish::Flag(flag) => flag == *self,
Publish::Registry(ref reg) => reg.is_empty() != *self,
}
}
}
impl PartialEq<bool> for Publish {
fn eq(&self, b: &bool) -> bool {
b.eq(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Badge {
pub repository: String,
#[serde(default = "default_master")]
pub branch: String,
pub service: Option<String>,
pub id: Option<String>,
#[serde(alias = "project_name")]
pub project_name: Option<String>,
}
fn default_master() -> String {
"master".to_string()
}
#[allow(clippy::unnecessary_wraps)]
fn ok_or_default<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: Deserialize<'de> + Default,
D: Deserializer<'de>,
{
Ok(Deserialize::deserialize(deserializer).unwrap_or_default())
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Badges {
#[serde(default, deserialize_with = "ok_or_default")]
pub appveyor: Option<Badge>,
#[serde(default, deserialize_with = "ok_or_default")]
pub circle_ci: Option<Badge>,
#[serde(default, deserialize_with = "ok_or_default")]
pub gitlab: Option<Badge>,
#[serde(default, deserialize_with = "ok_or_default")]
pub travis_ci: Option<Badge>,
#[serde(default, deserialize_with = "ok_or_default")]
pub codecov: Option<Badge>,
#[serde(default, deserialize_with = "ok_or_default")]
pub coveralls: Option<Badge>,
#[serde(default, deserialize_with = "ok_or_default")]
pub is_it_maintained_issue_resolution: Option<Badge>,
#[serde(default, deserialize_with = "ok_or_default")]
pub is_it_maintained_open_issues: Option<Badge>,
#[serde(default, deserialize_with = "ok_or_default")]
pub maintenance: Maintenance,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default, Serialize, Deserialize)]
pub struct Maintenance {
pub status: MaintenanceStatus,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum MaintenanceStatus {
None,
ActivelyDeveloped,
PassivelyMaintained,
AsIs,
Experimental,
LookingForMaintainer,
Deprecated,
}
impl Default for MaintenanceStatus {
fn default() -> Self {
MaintenanceStatus::None
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, Serialize, Deserialize)]
pub enum Edition {
#[serde(rename = "2015")]
E2015,
#[serde(rename = "2018")]
E2018,
#[serde(rename = "2021")]
E2021,
}
impl Default for Edition {
fn default() -> Self {
Edition::E2015
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, Serialize, Deserialize)]
pub enum Resolver {
#[serde(rename = "1")]
V1,
#[serde(rename = "2")]
V2,
}
impl Default for Resolver {
fn default() -> Self {
Self::V1
}
}
fn serialize_optional_tables_last<'a, I, K, V, S>(
data: &'a Option<I>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
&'a I: IntoIterator<Item = (K, V)>,
I: Serialize,
K: Serialize,
V: Serialize,
S: Serializer,
{
if let Some(d) = data {
toml::ser::tables_last(d, serializer)
} else {
None::<I>.serialize(serializer)
}
}