use std::fs;
use std::io;
use std::path::Path;
use toml;
#[macro_use]
extern crate serde_derive;
use serde::Deserialize;
use serde::Deserializer;
use std::collections::BTreeMap;
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;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Manifest<Metadata = Value> {
pub package: Option<Package<Metadata>>,
pub workspace: Option<Workspace>,
#[serde(default, serialize_with = "toml::ser::tables_last")]
pub dependencies: DepsSet,
#[serde(default, serialize_with = "toml::ser::tables_last")]
pub dev_dependencies: DepsSet,
#[serde(default, serialize_with = "toml::ser::tables_last")]
pub build_dependencies: DepsSet,
#[serde(default, serialize_with = "toml::ser::tables_last")]
pub target: TargetDepsSet,
#[serde(default, serialize_with = "toml::ser::tables_last")]
pub features: FeatureSet,
#[serde(default, serialize_with = "toml::ser::tables_last")]
pub patch: PatchSet,
pub lib: Option<Product>,
#[serde(default)]
pub profile: Profiles,
#[serde(default)]
pub badges: Badges,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub bin: Vec<Product>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub bench: Vec<Product>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub test: Vec<Product>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub example: Vec<Product>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub struct Workspace {
#[serde(default)]
pub members: Vec<String>,
#[serde(default)]
pub default_members: Vec<String>,
#[serde(default)]
pub exclude: Vec<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)
}
pub fn from_str(cargo_toml_content: &str) -> Result<Self, Error> {
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 package) = self.package {
let src = fs.file_names_in("src")?;
if let Some(ref mut lib) = self.lib {
lib.required_features.clear(); }
let has_path = self.lib.as_ref().map_or(false, |l| l.path.is_some());
if !has_path && src.contains("lib.rs") {
self.lib = Some(Product {
name: Some(package.name.replace("-", "_")),
path: Some("src/lib.rs".to_string()),
edition: Some(package.edition),
crate_type: Some(vec!["rlib".to_string()]),
..Product::default()
})
}
if package.autobins && self.bin.is_empty() {
self.bin = self.autoset("src/bin", &fs);
if src.contains("main.rs") {
self.bin.push(Product {
name: Some(package.name.clone()),
path: Some("src/main.rs".to_string()),
edition: Some(package.edition),
..Product::default()
})
}
}
if package.autoexamples && self.example.is_empty() {
self.example = self.autoset("examples", &fs);
}
if package.autotests && self.test.is_empty() {
self.test = self.autoset("tests", &fs);
}
if package.autobenches && self.bench.is_empty() {
self.bench = self.autoset("benches", &fs);
}
}
Ok(())
}
fn autoset(&self, dir: &str, fs: &dyn AbstractFilesystem) -> Vec<Product> {
let mut out = Vec::new();
if let Some(ref package) = self.package {
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: Some(package.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: Some(package.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>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Profile {
pub opt_level: Option<Value>,
pub debug: Option<Value>,
pub rpath: Option<bool>,
pub lto: Option<Value>,
pub debug_assertions: Option<bool>,
pub codegen_units: Option<u16>,
pub panic: Option<String>,
pub incremental: Option<bool>,
pub overflow_checks: Option<bool>,
#[serde(default, serialize_with = "toml::ser::tables_last")]
pub package: BTreeMap<String, Value>,
pub build_override: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, 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)]
pub proc_macro: bool,
#[serde(default = "default_true")]
pub harness: bool,
#[serde(default)]
pub edition: Option<Edition>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub crate_type: Option<Vec<String>>,
#[serde(default)]
pub required_features: 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, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Target {
#[serde(default, serialize_with = "toml::ser::tables_last")]
pub dependencies: DepsSet,
#[serde(default, serialize_with = "toml::ser::tables_last")]
pub dev_dependencies: DepsSet,
#[serde(default, serialize_with = "toml::ser::tables_last")]
pub build_dependencies: DepsSet,
}
#[derive(Debug, Clone, PartialEq, 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_ref().map(|s| s.as_str()).unwrap_or("*"),
}
}
pub fn req_features(&self) -> &[String] {
match *self {
Dependency::Simple(_) => &[],
Dependency::Detailed(ref d) => &d.features,
}
}
pub fn optional(&self) -> bool {
self.detail().map_or(false, |d| d.optional)
}
pub fn package(&self) -> Option<&str> {
match *self {
Dependency::Simple(_) => None,
Dependency::Detailed(ref d) => d.package.as_ref().map(|p| p.as_str()),
}
}
pub fn git(&self) -> Option<&str> {
self.detail()
.and_then(|d| d.git.as_ref().map(|p| p.as_str()))
}
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, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DependencyDetail {
pub version: Option<String>,
pub registry: Option<String>,
pub registry_index: Option<String>,
pub path: Option<String>,
pub git: Option<String>,
pub branch: Option<String>,
pub tag: Option<String>,
pub rev: Option<String>,
#[serde(default)]
pub features: Vec<String>,
#[serde(default)]
pub optional: bool,
pub default_features: Option<bool>,
pub package: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Package<Metadata = Value> {
pub name: String,
#[serde(default)]
pub edition: Edition,
pub version: String,
pub build: Option<Value>,
pub workspace: Option<String>,
#[serde(default)]
pub authors: Vec<String>,
pub links: Option<String>,
pub description: Option<String>,
pub homepage: Option<String>,
pub documentation: Option<String>,
pub readme: Option<String>,
#[serde(default)]
pub keywords: Vec<String>,
#[serde(default)]
pub categories: Vec<String>,
pub license: Option<String>,
pub license_file: Option<String>,
pub repository: Option<String>,
pub metadata: Option<Metadata>,
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(default)]
pub publish: Publish,
pub resolver: Option<Resolver>,
}
#[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, 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>,
pub project_name: Option<String>,
}
fn default_master() -> String {
"master".to_string()
}
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, 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 {
Self::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
}
}