use std::iter::FromIterator;
use toml_edit;
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
enum DependencySource {
Version {
version: Option<String>,
path: Option<String>,
registry: Option<String>,
},
Git {
repo: String,
branch: Option<String>,
},
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub struct Dependency {
pub name: String,
optional: bool,
pub features: Option<Vec<String>>,
default_features: bool,
source: DependencySource,
rename: Option<String>,
}
impl Default for Dependency {
fn default() -> Dependency {
Dependency {
name: "".into(),
rename: None,
optional: false,
features: None,
default_features: true,
source: DependencySource::Version {
version: None,
path: None,
registry: None,
},
}
}
}
impl Dependency {
pub fn new(name: &str) -> Dependency {
Dependency {
name: name.into(),
..Dependency::default()
}
}
pub fn set_version(mut self, version: &str) -> Dependency {
let version = version.split('+').next().unwrap();
let (old_path, old_registry) = match self.source {
DependencySource::Version { path, registry, .. } => (path, registry),
_ => (None, None),
};
self.source = DependencySource::Version {
version: Some(version.into()),
path: old_path,
registry: old_registry,
};
self
}
pub fn set_git(mut self, repo: &str, branch: Option<String>) -> Dependency {
self.source = DependencySource::Git {
repo: repo.into(),
branch,
};
self
}
pub fn set_path(mut self, path: &str) -> Dependency {
let old_version = match self.source {
DependencySource::Version { version, .. } => version,
_ => None,
};
self.source = DependencySource::Version {
version: old_version,
path: Some(path.replace('\\', "/")),
registry: None,
};
self
}
pub fn set_optional(mut self, opt: bool) -> Dependency {
self.optional = opt;
self
}
pub fn set_features(mut self, features: Option<Vec<String>>) -> Dependency {
self.features = features.map(|f| {
f.iter()
.map(|x| x.split(' ').map(String::from))
.flatten()
.filter(|s| !s.is_empty())
.collect::<Vec<String>>()
});
self
}
pub fn set_default_features(mut self, default_features: bool) -> Dependency {
self.default_features = default_features;
self
}
pub fn set_rename(mut self, rename: &str) -> Dependency {
self.rename = Some(rename.into());
self
}
pub fn name_in_manifest(&self) -> &str {
&self.rename().unwrap_or(&self.name)
}
pub fn set_registry(mut self, registry: &str) -> Dependency {
let old_version = match self.source {
DependencySource::Version { version, .. } => version,
_ => None,
};
self.source = DependencySource::Version {
version: old_version,
path: None,
registry: Some(registry.into()),
};
self
}
pub fn version(&self) -> Option<&str> {
if let DependencySource::Version {
version: Some(ref version),
..
} = self.source
{
Some(version)
} else {
None
}
}
pub fn rename(&self) -> Option<&str> {
match &self.rename {
Some(rename) => Some(&rename),
None => None,
}
}
pub fn to_toml(&self) -> (String, toml_edit::Item) {
let data: toml_edit::Item = match (
self.optional,
self.features.as_ref(),
self.default_features,
self.source.clone(),
self.rename.as_ref(),
) {
(
false,
None,
true,
DependencySource::Version {
version: Some(v),
path: None,
registry: None,
},
None,
) => toml_edit::value(v),
(optional, features, default_features, source, rename) => {
let mut data = toml_edit::InlineTable::default();
match source {
DependencySource::Version {
version,
path,
registry,
} => {
if let Some(v) = version {
data.get_or_insert("version", v);
}
if let Some(p) = path {
data.get_or_insert("path", p);
}
if let Some(r) = registry {
data.get_or_insert("registry", r);
}
}
DependencySource::Git { repo, branch } => {
data.get_or_insert("git", repo);
branch.map(|branch| data.get_or_insert("branch", branch));
}
}
if self.optional {
data.get_or_insert("optional", optional);
}
if let Some(features) = features {
let features = toml_edit::Value::from_iter(features.iter().cloned());
data.get_or_insert("features", features);
}
if !self.default_features {
data.get_or_insert("default-features", default_features);
}
if rename.is_some() {
data.get_or_insert("package", self.name.clone());
}
data.fmt();
toml_edit::value(toml_edit::Value::InlineTable(data))
}
};
(self.name_in_manifest().to_string(), data)
}
}
#[cfg(test)]
mod tests {
use crate::dependency::Dependency;
#[test]
fn to_toml_simple_dep() {
let toml = Dependency::new("dep").to_toml();
assert_eq!(toml.0, "dep".to_owned());
}
#[test]
fn to_toml_simple_dep_with_version() {
let toml = Dependency::new("dep").set_version("1.0").to_toml();
assert_eq!(toml.0, "dep".to_owned());
assert_eq!(toml.1.as_str(), Some("1.0"));
}
#[test]
fn to_toml_optional_dep() {
let toml = Dependency::new("dep").set_optional(true).to_toml();
assert_eq!(toml.0, "dep".to_owned());
assert!(toml.1.is_inline_table());
let dep = toml.1.as_inline_table().unwrap();
assert_eq!(dep.get("optional").unwrap().as_bool(), Some(true));
}
#[test]
fn to_toml_dep_without_default_features() {
let toml = Dependency::new("dep").set_default_features(false).to_toml();
assert_eq!(toml.0, "dep".to_owned());
assert!(toml.1.is_inline_table());
let dep = toml.1.as_inline_table().unwrap();
assert_eq!(dep.get("default-features").unwrap().as_bool(), Some(false));
}
#[test]
fn to_toml_dep_with_path_source() {
let toml = Dependency::new("dep").set_path("~/foo/bar").to_toml();
assert_eq!(toml.0, "dep".to_owned());
assert!(toml.1.is_inline_table());
let dep = toml.1.as_inline_table().unwrap();
assert_eq!(dep.get("path").unwrap().as_str(), Some("~/foo/bar"));
}
#[test]
fn to_toml_dep_with_git_source() {
let toml = Dependency::new("dep")
.set_git("https://foor/bar.git", None)
.to_toml();
assert_eq!(toml.0, "dep".to_owned());
assert!(toml.1.is_inline_table());
let dep = toml.1.as_inline_table().unwrap();
assert_eq!(
dep.get("git").unwrap().as_str(),
Some("https://foor/bar.git")
);
}
#[test]
fn to_toml_renamed_dep() {
let toml = Dependency::new("dep").set_rename("d").to_toml();
assert_eq!(toml.0, "d".to_owned());
assert!(toml.1.is_inline_table());
let dep = toml.1.as_inline_table().unwrap();
assert_eq!(dep.get("package").unwrap().as_str(), Some("dep"));
}
#[test]
fn to_toml_dep_from_alt_registry() {
let toml = Dependency::new("dep").set_registry("alternative").to_toml();
assert_eq!(toml.0, "dep".to_owned());
assert!(toml.1.is_inline_table());
let dep = toml.1.as_inline_table().unwrap();
assert_eq!(dep.get("registry").unwrap().as_str(), Some("alternative"));
}
#[test]
fn to_toml_complex_dep() {
let toml = Dependency::new("dep")
.set_version("1.0")
.set_default_features(false)
.set_rename("d")
.to_toml();
assert_eq!(toml.0, "d".to_owned());
assert!(toml.1.is_inline_table());
let dep = toml.1.as_inline_table().unwrap();
assert_eq!(dep.get("package").unwrap().as_str(), Some("dep"));
assert_eq!(dep.get("version").unwrap().as_str(), Some("1.0"));
assert_eq!(dep.get("default-features").unwrap().as_bool(), Some(false));
}
#[test]
fn paths_with_forward_slashes_are_left_as_is() {
let path = "../sibling/crate";
let dep = Dependency::new("dep").set_path(path);
let (_, toml) = dep.to_toml();
let table = toml.as_inline_table().unwrap();
let got = table.get("path").unwrap().as_str().unwrap();
assert_eq!(got, path);
}
#[test]
fn normalise_windows_style_paths() {
let original = r"..\sibling\crate";
let should_be = "../sibling/crate";
let dep = Dependency::new("dep").set_path(original);
let (_, toml) = dep.to_toml();
let table = toml.as_inline_table().unwrap();
let got = table.get("path").unwrap().as_str().unwrap();
assert_eq!(got, should_be);
}
}