use std::borrow::Cow;
use std::fmt::{Display, Formatter};
use std::path::{Path, PathBuf};
use cargo_util_schemas::manifest::PathBaseName;
use indexmap::IndexSet;
use itertools::Itertools;
use toml_edit::KeyMut;
use super::manifest::str_or_1_len_table;
use crate::core::SourceId;
use crate::core::Summary;
use crate::core::{Features, GitReference};
use crate::util::toml::lookup_path_base;
use crate::util::toml_mut::is_sorted;
use crate::CargoResult;
use crate::GlobalContext;
#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub struct Dependency {
pub name: String,
pub optional: Option<bool>,
pub public: Option<bool>,
pub features: Option<IndexSet<String>>,
pub default_features: Option<bool>,
pub inherited_features: Option<IndexSet<String>>,
pub source: Option<Source>,
pub registry: Option<String>,
pub rename: Option<String>,
}
impl Dependency {
pub fn new(name: &str) -> Self {
Self {
name: name.into(),
optional: None,
public: None,
features: None,
default_features: None,
inherited_features: None,
source: None,
registry: None,
rename: None,
}
}
pub fn set_source(mut self, source: impl Into<Source>) -> Self {
self.source = Some(source.into());
self
}
pub fn clear_version(mut self) -> Self {
match &mut self.source {
Some(Source::Registry(_)) => {
self.source = None;
}
Some(Source::Path(path)) => {
path.version = None;
}
Some(Source::Git(git)) => {
git.version = None;
}
Some(Source::Workspace(_workspace)) => {}
None => {}
}
self
}
#[allow(dead_code)]
pub fn set_optional(mut self, opt: bool) -> Self {
self.optional = Some(opt);
self
}
#[allow(dead_code)]
pub fn set_features(mut self, features: IndexSet<String>) -> Self {
self.features = Some(features);
self
}
pub fn extend_features(mut self, features: impl IntoIterator<Item = String>) -> Self {
self.features
.get_or_insert_with(Default::default)
.extend(features);
self
}
#[allow(dead_code)]
pub fn set_default_features(mut self, default_features: bool) -> Self {
self.default_features = Some(default_features);
self
}
pub fn set_rename(mut self, rename: &str) -> Self {
self.rename = Some(rename.into());
self
}
pub fn set_registry(mut self, registry: impl Into<String>) -> Self {
self.registry = Some(registry.into());
self
}
pub fn set_inherited_features(mut self, features: IndexSet<String>) -> Self {
self.inherited_features = Some(features);
self
}
pub fn source(&self) -> Option<&Source> {
self.source.as_ref()
}
pub fn version(&self) -> Option<&str> {
match self.source()? {
Source::Registry(src) => Some(src.version.as_str()),
Source::Path(src) => src.version.as_deref(),
Source::Git(src) => src.version.as_deref(),
Source::Workspace(_) => None,
}
}
pub fn registry(&self) -> Option<&str> {
self.registry.as_deref()
}
pub fn rename(&self) -> Option<&str> {
self.rename.as_deref()
}
pub fn default_features(&self) -> Option<bool> {
self.default_features
}
pub fn optional(&self) -> Option<bool> {
self.optional
}
pub fn public(&self) -> Option<bool> {
self.public
}
pub fn source_id(&self, gctx: &GlobalContext) -> CargoResult<MaybeWorkspace<SourceId>> {
match &self.source.as_ref() {
Some(Source::Registry(_)) | None => {
if let Some(r) = self.registry() {
let source_id = SourceId::alt_registry(gctx, r)?;
Ok(MaybeWorkspace::Other(source_id))
} else {
let source_id = SourceId::crates_io(gctx)?;
Ok(MaybeWorkspace::Other(source_id))
}
}
Some(Source::Path(source)) => Ok(MaybeWorkspace::Other(source.source_id()?)),
Some(Source::Git(source)) => Ok(MaybeWorkspace::Other(source.source_id()?)),
Some(Source::Workspace(workspace)) => Ok(MaybeWorkspace::Workspace(workspace.clone())),
}
}
pub fn query(
&self,
gctx: &GlobalContext,
) -> CargoResult<MaybeWorkspace<crate::core::dependency::Dependency>> {
let source_id = self.source_id(gctx)?;
match source_id {
MaybeWorkspace::Workspace(workspace) => Ok(MaybeWorkspace::Workspace(workspace)),
MaybeWorkspace::Other(source_id) => Ok(MaybeWorkspace::Other(
crate::core::dependency::Dependency::parse(
self.name.as_str(),
self.version(),
source_id,
)?,
)),
}
}
}
pub enum MaybeWorkspace<T> {
Workspace(WorkspaceSource),
Other(T),
}
impl Dependency {
pub fn from_toml(
gctx: &GlobalContext,
workspace_root: &Path,
crate_root: &Path,
unstable_features: &Features,
key: &str,
item: &toml_edit::Item,
) -> CargoResult<Self> {
if let Some(version) = item.as_str() {
let dep = Self::new(key).set_source(RegistrySource::new(version));
Ok(dep)
} else if let Some(table) = item.as_table_like() {
let (name, rename) = if let Some(value) = table.get("package") {
(
value
.as_str()
.ok_or_else(|| invalid_type(key, "package", value.type_name(), "string"))?
.to_owned(),
Some(key.to_owned()),
)
} else {
(key.to_owned(), None)
};
let source: Source = if let Some(git) = table.get("git") {
let mut src = GitSource::new(
git.as_str()
.ok_or_else(|| invalid_type(key, "git", git.type_name(), "string"))?,
);
if let Some(value) = table.get("branch") {
src =
src.set_branch(value.as_str().ok_or_else(|| {
invalid_type(key, "branch", value.type_name(), "string")
})?);
}
if let Some(value) = table.get("tag") {
src =
src.set_tag(value.as_str().ok_or_else(|| {
invalid_type(key, "tag", value.type_name(), "string")
})?);
}
if let Some(value) = table.get("rev") {
src =
src.set_rev(value.as_str().ok_or_else(|| {
invalid_type(key, "rev", value.type_name(), "string")
})?);
}
if let Some(value) = table.get("version") {
src = src.set_version(value.as_str().ok_or_else(|| {
invalid_type(key, "version", value.type_name(), "string")
})?);
}
src.into()
} else if let Some(path) = table.get("path") {
let base = table
.get("base")
.map(|base| {
base.as_str()
.ok_or_else(|| invalid_type(key, "base", base.type_name(), "string"))
.map(|s| s.to_owned())
})
.transpose()?;
let relative_to = if let Some(base) = &base {
Cow::Owned(lookup_path_base(
&PathBaseName::new(base.clone())?,
gctx,
&|| Ok(workspace_root),
unstable_features,
)?)
} else {
Cow::Borrowed(crate_root)
};
let path =
relative_to
.join(path.as_str().ok_or_else(|| {
invalid_type(key, "path", path.type_name(), "string")
})?);
let mut src = PathSource::new(path);
src.base = base;
if let Some(value) = table.get("version") {
src = src.set_version(value.as_str().ok_or_else(|| {
invalid_type(key, "version", value.type_name(), "string")
})?);
}
src.into()
} else if let Some(version) = table.get("version") {
let src =
RegistrySource::new(version.as_str().ok_or_else(|| {
invalid_type(key, "version", version.type_name(), "string")
})?);
src.into()
} else if let Some(workspace) = table.get("workspace") {
let workspace_bool = workspace
.as_bool()
.ok_or_else(|| invalid_type(key, "workspace", workspace.type_name(), "bool"))?;
if !workspace_bool {
anyhow::bail!("`{key}.workspace = false` is unsupported")
}
let src = WorkspaceSource::new();
src.into()
} else {
let mut msg = format!("unrecognized dependency source for `{key}`");
if table.is_empty() {
msg.push_str(
", expected a local path, Git repository, version, or workspace dependency to be specified",
);
}
anyhow::bail!(msg);
};
let registry = if let Some(value) = table.get("registry") {
Some(
value
.as_str()
.ok_or_else(|| invalid_type(key, "registry", value.type_name(), "string"))?
.to_owned(),
)
} else {
None
};
let default_features = table.get("default-features").and_then(|v| v.as_bool());
if table.contains_key("default_features") {
anyhow::bail!("Use of `default_features` in `{key}` is unsupported, please switch to `default-features`");
}
let features = if let Some(value) = table.get("features") {
Some(
value
.as_array()
.ok_or_else(|| invalid_type(key, "features", value.type_name(), "array"))?
.iter()
.map(|v| {
v.as_str().map(|s| s.to_owned()).ok_or_else(|| {
invalid_type(key, "features", v.type_name(), "string")
})
})
.collect::<CargoResult<IndexSet<String>>>()?,
)
} else {
None
};
let optional = table.get("optional").and_then(|v| v.as_bool());
let public = table.get("public").and_then(|v| v.as_bool());
let dep = Self {
name,
optional,
public,
features,
default_features,
inherited_features: None,
source: Some(source),
registry,
rename,
};
Ok(dep)
} else {
anyhow::bail!("Unrecognized` dependency entry format for `{key}");
}
}
pub fn toml_key(&self) -> &str {
self.rename().unwrap_or(&self.name)
}
pub fn to_toml<'a>(
&self,
gctx: &GlobalContext,
workspace_root: &Path,
crate_root: &Path,
unstable_features: &Features,
) -> CargoResult<toml_edit::Item> {
assert!(
crate_root.is_absolute(),
"Absolute path needed, got: {}",
crate_root.display()
);
let table: toml_edit::Item = match (
self.public.unwrap_or(false),
self.optional.unwrap_or(false),
self.features.as_ref(),
self.default_features.unwrap_or(true),
self.source.as_ref(),
self.registry.as_ref(),
self.rename.as_ref(),
) {
(
false,
false,
None,
true,
Some(Source::Registry(RegistrySource { version: v })),
None,
None,
) => toml_edit::value(v),
(false, false, None, true, Some(Source::Workspace(WorkspaceSource {})), None, None) => {
let mut table = toml_edit::InlineTable::default();
table.set_dotted(true);
table.insert("workspace", true.into());
toml_edit::value(toml_edit::Value::InlineTable(table))
}
(_, _, _, _, _, _, _) => {
let mut table = toml_edit::InlineTable::default();
match &self.source {
Some(Source::Registry(src)) => {
table.insert("version", src.version.as_str().into());
}
Some(Source::Path(src)) => {
let relpath =
path_field(&src, gctx, workspace_root, crate_root, unstable_features)?;
if let Some(r) = src.version.as_deref() {
table.insert("version", r.into());
}
if let Some(base) = &src.base {
table.insert("base", base.into());
}
table.insert("path", relpath.into());
}
Some(Source::Git(src)) => {
table.insert("git", src.git.as_str().into());
if let Some(branch) = src.branch.as_deref() {
table.insert("branch", branch.into());
}
if let Some(tag) = src.tag.as_deref() {
table.insert("tag", tag.into());
}
if let Some(rev) = src.rev.as_deref() {
table.insert("rev", rev.into());
}
if let Some(r) = src.version.as_deref() {
table.insert("version", r.into());
}
}
Some(Source::Workspace(_)) => {
table.insert("workspace", true.into());
}
None => {}
}
if table.contains_key("version") {
if let Some(r) = self.registry.as_deref() {
table.insert("registry", r.into());
}
}
if self.rename.is_some() {
table.insert("package", self.name.as_str().into());
}
if let Some(v) = self.default_features {
table.insert("default-features", v.into());
}
if let Some(features) = self.features.as_ref() {
let features: toml_edit::Value = features.iter().cloned().collect();
table.insert("features", features);
}
if let Some(v) = self.optional {
table.insert("optional", v.into());
}
if let Some(v) = self.public {
table.insert("public", v.into());
}
toml_edit::value(toml_edit::Value::InlineTable(table))
}
};
Ok(table)
}
pub fn update_toml<'k, 'a>(
&self,
gctx: &GlobalContext,
workspace_root: &Path,
crate_root: &Path,
unstable_features: &Features,
key: &mut KeyMut<'k>,
item: &mut toml_edit::Item,
) -> CargoResult<()> {
if str_or_1_len_table(item) {
let mut new_item = self.to_toml(gctx, workspace_root, crate_root, unstable_features)?;
match (&item, &mut new_item) {
(toml_edit::Item::Value(old), toml_edit::Item::Value(new)) => {
*new.decor_mut() = old.decor().clone();
}
(toml_edit::Item::Table(old), toml_edit::Item::Table(new)) => {
*new.decor_mut() = old.decor().clone();
}
(_, _) => {}
}
*item = new_item;
} else if let Some(table) = item.as_table_like_mut() {
match &self.source {
Some(Source::Registry(src)) => {
overwrite_value(table, "version", src.version.as_str());
for key in ["path", "git", "branch", "tag", "rev", "workspace", "base"] {
table.remove(key);
}
}
Some(Source::Path(src)) => {
if let Some(base) = &src.base {
overwrite_value(table, "base", base);
} else {
table.remove("base");
}
let relpath =
path_field(&src, gctx, workspace_root, crate_root, unstable_features)?;
overwrite_value(table, "path", relpath);
if let Some(r) = src.version.as_deref() {
overwrite_value(table, "version", r);
} else {
table.remove("version");
}
for key in ["git", "branch", "tag", "rev", "workspace"] {
table.remove(key);
}
}
Some(Source::Git(src)) => {
overwrite_value(table, "git", src.git.as_str());
if let Some(branch) = src.branch.as_deref() {
overwrite_value(table, "branch", branch);
} else {
table.remove("branch");
}
if let Some(tag) = src.tag.as_deref() {
overwrite_value(table, "tag", tag);
} else {
table.remove("tag");
}
if let Some(rev) = src.rev.as_deref() {
overwrite_value(table, "rev", rev);
} else {
table.remove("rev");
}
if let Some(r) = src.version.as_deref() {
overwrite_value(table, "version", r);
} else {
table.remove("version");
}
for key in ["path", "workspace", "base"] {
table.remove(key);
}
}
Some(Source::Workspace(_)) => {
overwrite_value(table, "workspace", true);
table.set_dotted(true);
key.fmt();
for key in [
"version",
"registry",
"registry-index",
"path",
"git",
"branch",
"tag",
"rev",
"package",
"default-features",
"base",
] {
table.remove(key);
}
}
None => {}
}
if table.contains_key("version") {
if let Some(r) = self.registry.as_deref() {
overwrite_value(table, "registry", r);
} else {
table.remove("registry");
}
} else {
table.remove("registry");
}
if self.rename.is_some() {
overwrite_value(table, "package", self.name.as_str());
}
match self.default_features {
Some(v) => {
overwrite_value(table, "default-features", v);
}
None => {
table.remove("default-features");
}
}
if let Some(new_features) = self.features.as_ref() {
let mut features = table
.get("features")
.and_then(|i| i.as_value())
.and_then(|v| v.as_array())
.and_then(|a| {
a.iter()
.map(|v| v.as_str())
.collect::<Option<IndexSet<_>>>()
})
.unwrap_or_default();
let is_already_sorted = is_sorted(features.iter());
features.extend(new_features.iter().map(|s| s.as_str()));
let features = if is_already_sorted {
features.into_iter().sorted().collect::<toml_edit::Value>()
} else {
features.into_iter().collect::<toml_edit::Value>()
};
table.set_dotted(false);
overwrite_value(table, "features", features);
} else {
table.remove("features");
}
match self.optional {
Some(v) => {
table.set_dotted(false);
overwrite_value(table, "optional", v);
}
None => {
table.remove("optional");
}
}
match self.public {
Some(v) => {
table.set_dotted(false);
overwrite_value(table, "public", v);
}
None => {
table.remove("public");
}
}
} else {
unreachable!("Invalid dependency type: {}", item.type_name());
}
Ok(())
}
}
fn overwrite_value(
table: &mut dyn toml_edit::TableLike,
key: &str,
value: impl Into<toml_edit::Value>,
) {
let mut value = value.into();
let existing = table.entry(key).or_insert_with(|| Default::default());
if let Some(existing_value) = existing.as_value() {
*value.decor_mut() = existing_value.decor().clone();
}
*existing = toml_edit::Item::Value(value);
}
fn invalid_type(dep: &str, key: &str, actual: &str, expected: &str) -> anyhow::Error {
anyhow::format_err!("Found {actual} for {key} when {expected} was expected for {dep}")
}
impl std::fmt::Display for Dependency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(source) = self.source() {
write!(f, "{}@{}", self.name, source)
} else {
self.toml_key().fmt(f)
}
}
}
impl<'s> From<&'s Summary> for Dependency {
fn from(other: &'s Summary) -> Self {
let source: Source = if let Some(path) = other.source_id().local_path() {
PathSource::new(path)
.set_version(other.version().to_string())
.into()
} else if let Some(git_ref) = other.source_id().git_reference() {
let mut src = GitSource::new(other.source_id().url().to_string())
.set_version(other.version().to_string());
match git_ref {
GitReference::Branch(branch) => src = src.set_branch(branch),
GitReference::Tag(tag) => src = src.set_tag(tag),
GitReference::Rev(rev) => src = src.set_rev(rev),
GitReference::DefaultBranch => {}
}
src.into()
} else {
RegistrySource::new(other.version().to_string()).into()
};
Dependency::new(other.name().as_str()).set_source(source)
}
}
impl From<Summary> for Dependency {
fn from(other: Summary) -> Self {
(&other).into()
}
}
fn path_field<'a>(
source: &PathSource,
gctx: &GlobalContext,
workspace_root: &Path,
crate_root: &Path,
unstable_features: &Features,
) -> CargoResult<String> {
let relative_to = if let Some(base) = &source.base {
Cow::Owned(lookup_path_base(
&PathBaseName::new(base.clone())?,
gctx,
&|| Ok(workspace_root),
unstable_features,
)?)
} else {
Cow::Borrowed(crate_root)
};
let relpath = pathdiff::diff_paths(&source.path, relative_to).expect("both paths are absolute");
let relpath = relpath.to_str().unwrap().replace('\\', "/");
Ok(relpath)
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub enum Source {
Registry(RegistrySource),
Path(PathSource),
Git(GitSource),
Workspace(WorkspaceSource),
}
impl Source {
pub fn as_registry(&self) -> Option<&RegistrySource> {
match self {
Self::Registry(src) => Some(src),
_ => None,
}
}
#[allow(dead_code)]
pub fn as_path(&self) -> Option<&PathSource> {
match self {
Self::Path(src) => Some(src),
_ => None,
}
}
#[allow(dead_code)]
pub fn as_git(&self) -> Option<&GitSource> {
match self {
Self::Git(src) => Some(src),
_ => None,
}
}
#[allow(dead_code)]
pub fn as_workspace(&self) -> Option<&WorkspaceSource> {
match self {
Self::Workspace(src) => Some(src),
_ => None,
}
}
}
impl std::fmt::Display for Source {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Registry(src) => src.fmt(f),
Self::Path(src) => src.fmt(f),
Self::Git(src) => src.fmt(f),
Self::Workspace(src) => src.fmt(f),
}
}
}
impl<'s> From<&'s Source> for Source {
fn from(inner: &'s Source) -> Self {
inner.clone()
}
}
impl From<RegistrySource> for Source {
fn from(inner: RegistrySource) -> Self {
Self::Registry(inner)
}
}
impl From<PathSource> for Source {
fn from(inner: PathSource) -> Self {
Self::Path(inner)
}
}
impl From<GitSource> for Source {
fn from(inner: GitSource) -> Self {
Self::Git(inner)
}
}
impl From<WorkspaceSource> for Source {
fn from(inner: WorkspaceSource) -> Self {
Self::Workspace(inner)
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub struct RegistrySource {
pub version: String,
}
impl RegistrySource {
pub fn new(version: impl AsRef<str>) -> Self {
let version = version.as_ref().split('+').next().unwrap();
Self {
version: version.to_owned(),
}
}
}
impl std::fmt::Display for RegistrySource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.version.fmt(f)
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub struct PathSource {
pub path: PathBuf,
pub base: Option<String>,
pub version: Option<String>,
}
impl PathSource {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into(),
base: None,
version: None,
}
}
pub fn set_version(mut self, version: impl AsRef<str>) -> Self {
let version = version.as_ref().split('+').next().unwrap();
self.version = Some(version.to_owned());
self
}
pub fn source_id(&self) -> CargoResult<SourceId> {
SourceId::for_path(&self.path)
}
}
impl std::fmt::Display for PathSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.path.display().fmt(f)
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub struct GitSource {
pub git: String,
pub branch: Option<String>,
pub tag: Option<String>,
pub rev: Option<String>,
pub version: Option<String>,
}
impl GitSource {
pub fn new(git: impl Into<String>) -> Self {
Self {
git: git.into(),
branch: None,
tag: None,
rev: None,
version: None,
}
}
pub fn set_branch(mut self, branch: impl Into<String>) -> Self {
self.branch = Some(branch.into());
self.tag = None;
self.rev = None;
self
}
pub fn set_tag(mut self, tag: impl Into<String>) -> Self {
self.branch = None;
self.tag = Some(tag.into());
self.rev = None;
self
}
pub fn set_rev(mut self, rev: impl Into<String>) -> Self {
self.branch = None;
self.tag = None;
self.rev = Some(rev.into());
self
}
pub fn source_id(&self) -> CargoResult<SourceId> {
let git_url = self.git.parse::<url::Url>()?;
let git_ref = self.git_ref();
SourceId::for_git(&git_url, git_ref)
}
fn git_ref(&self) -> GitReference {
match (
self.branch.as_deref(),
self.tag.as_deref(),
self.rev.as_deref(),
) {
(Some(branch), _, _) => GitReference::Branch(branch.to_owned()),
(_, Some(tag), _) => GitReference::Tag(tag.to_owned()),
(_, _, Some(rev)) => GitReference::Rev(rev.to_owned()),
_ => GitReference::DefaultBranch,
}
}
pub fn set_version(mut self, version: impl AsRef<str>) -> Self {
let version = version.as_ref().split('+').next().unwrap();
self.version = Some(version.to_owned());
self
}
}
impl std::fmt::Display for GitSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let git_ref = self.git_ref();
if let Some(pretty_ref) = git_ref.pretty_ref(true) {
write!(f, "{}?{}", self.git, pretty_ref)
} else {
write!(f, "{}", self.git)
}
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub struct WorkspaceSource;
impl WorkspaceSource {
pub fn new() -> Self {
Self
}
}
impl Display for WorkspaceSource {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
"workspace".fmt(f)
}
}
#[cfg(test)]
mod tests {
use crate::util::toml_mut::manifest::LocalManifest;
use cargo_util::paths;
use super::*;
#[test]
fn to_toml_simple_dep() {
let crate_root =
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let dep = Dependency::new("dep").set_source(RegistrySource::new("1.0"));
let key = dep.toml_key();
let gctx = GlobalContext::default().unwrap();
let item = dep
.to_toml(&gctx, &crate_root, &crate_root, &Features::default())
.unwrap();
assert_eq!(key, "dep".to_owned());
verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
fn to_toml_simple_dep_with_version() {
let crate_root =
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let dep = Dependency::new("dep").set_source(RegistrySource::new("1.0"));
let key = dep.toml_key();
let gctx = GlobalContext::default().unwrap();
let item = dep
.to_toml(&gctx, &crate_root, &crate_root, &Features::default())
.unwrap();
assert_eq!(key, "dep".to_owned());
assert_eq!(item.as_str(), Some("1.0"));
verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
fn to_toml_optional_dep() {
let crate_root =
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let dep = Dependency::new("dep")
.set_source(RegistrySource::new("1.0"))
.set_optional(true);
let key = dep.toml_key();
let gctx = GlobalContext::default().unwrap();
let item = dep
.to_toml(&gctx, &crate_root, &crate_root, &Features::default())
.unwrap();
assert_eq!(key, "dep".to_owned());
assert!(item.is_inline_table());
let dep = item.as_inline_table().unwrap();
assert_eq!(dep.get("optional").unwrap().as_bool(), Some(true));
verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
fn to_toml_dep_without_default_features() {
let crate_root =
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let dep = Dependency::new("dep")
.set_source(RegistrySource::new("1.0"))
.set_default_features(false);
let key = dep.toml_key();
let gctx = GlobalContext::default().unwrap();
let item = dep
.to_toml(&gctx, &crate_root, &crate_root, &Features::default())
.unwrap();
assert_eq!(key, "dep".to_owned());
assert!(item.is_inline_table());
let dep = item.as_inline_table().unwrap();
assert_eq!(dep.get("default-features").unwrap().as_bool(), Some(false));
verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
fn to_toml_dep_with_path_source() {
let root = paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let crate_root = root.join("foo");
let dep = Dependency::new("dep").set_source(PathSource::new(root.join("bar")));
let key = dep.toml_key();
let gctx = GlobalContext::default().unwrap();
let item = dep
.to_toml(&gctx, &crate_root, &crate_root, &Features::default())
.unwrap();
assert_eq!(key, "dep".to_owned());
assert!(item.is_inline_table());
let dep = item.as_inline_table().unwrap();
assert_eq!(dep.get("path").unwrap().as_str(), Some("../bar"));
verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
fn to_toml_dep_with_git_source() {
let crate_root =
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let dep = Dependency::new("dep").set_source(GitSource::new("https://foor/bar.git"));
let key = dep.toml_key();
let gctx = GlobalContext::default().unwrap();
let item = dep
.to_toml(&gctx, &crate_root, &crate_root, &Features::default())
.unwrap();
assert_eq!(key, "dep".to_owned());
assert!(item.is_inline_table());
let dep = item.as_inline_table().unwrap();
assert_eq!(
dep.get("git").unwrap().as_str(),
Some("https://foor/bar.git")
);
verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
fn to_toml_renamed_dep() {
let crate_root =
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let dep = Dependency::new("dep")
.set_source(RegistrySource::new("1.0"))
.set_rename("d");
let key = dep.toml_key();
let gctx = GlobalContext::default().unwrap();
let item = dep
.to_toml(&gctx, &crate_root, &crate_root, &Features::default())
.unwrap();
assert_eq!(key, "d".to_owned());
assert!(item.is_inline_table());
let dep = item.as_inline_table().unwrap();
assert_eq!(dep.get("package").unwrap().as_str(), Some("dep"));
verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
fn to_toml_dep_from_alt_registry() {
let crate_root =
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let dep = Dependency::new("dep")
.set_source(RegistrySource::new("1.0"))
.set_registry("alternative");
let key = dep.toml_key();
let gctx = GlobalContext::default().unwrap();
let item = dep
.to_toml(&gctx, &crate_root, &crate_root, &Features::default())
.unwrap();
assert_eq!(key, "dep".to_owned());
assert!(item.is_inline_table());
let dep = item.as_inline_table().unwrap();
assert_eq!(dep.get("registry").unwrap().as_str(), Some("alternative"));
verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
fn to_toml_complex_dep() {
let crate_root =
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let dep = Dependency::new("dep")
.set_source(RegistrySource::new("1.0"))
.set_default_features(false)
.set_rename("d");
let key = dep.toml_key();
let gctx = GlobalContext::default().unwrap();
let item = dep
.to_toml(&gctx, &crate_root, &crate_root, &Features::default())
.unwrap();
assert_eq!(key, "d".to_owned());
assert!(item.is_inline_table());
let dep = item.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));
verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
fn paths_with_forward_slashes_are_left_as_is() {
let crate_root =
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let path = crate_root.join("sibling/crate");
let relpath = "sibling/crate";
let dep = Dependency::new("dep").set_source(PathSource::new(path));
let key = dep.toml_key();
let gctx = GlobalContext::default().unwrap();
let item = dep
.to_toml(&gctx, &crate_root, &crate_root, &Features::default())
.unwrap();
let table = item.as_inline_table().unwrap();
let got = table.get("path").unwrap().as_str().unwrap();
assert_eq!(got, relpath);
verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
fn overwrite_with_workspace_source_fmt_key() {
let crate_root =
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("./")));
let toml = "dep = \"1.0\"\n";
let manifest = toml.parse().unwrap();
let mut local = LocalManifest {
path: crate_root.clone(),
manifest,
};
assert_eq!(local.manifest.to_string(), toml);
let gctx = GlobalContext::default().unwrap();
for (key, item) in local.data.clone().iter() {
let dep = Dependency::from_toml(
&gctx,
&crate_root,
&crate_root,
&Features::default(),
key,
item,
)
.unwrap();
let dep = dep.set_source(WorkspaceSource::new());
local
.insert_into_table(&vec![], &dep, &gctx, &crate_root, &Features::default())
.unwrap();
assert_eq!(local.data.to_string(), "dep.workspace = true\n");
}
}
#[test]
#[cfg(windows)]
fn normalise_windows_style_paths() {
let crate_root =
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let original = crate_root.join(r"sibling\crate");
let should_be = "sibling/crate";
let dep = Dependency::new("dep").set_source(PathSource::new(original));
let key = dep.toml_key();
let gctx = GlobalContext::default().unwrap();
let item = dep
.to_toml(&gctx, &crate_root, &crate_root, &Features::default())
.unwrap();
let table = item.as_inline_table().unwrap();
let got = table.get("path").unwrap().as_str().unwrap();
assert_eq!(got, should_be);
verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[track_caller]
fn verify_roundtrip(
crate_root: &Path,
gctx: &GlobalContext,
key: &str,
item: &toml_edit::Item,
) {
let roundtrip = Dependency::from_toml(
gctx,
crate_root,
crate_root,
&Features::default(),
key,
item,
)
.unwrap();
let round_key = roundtrip.toml_key();
let round_item = roundtrip
.to_toml(gctx, crate_root, crate_root, &Features::default())
.unwrap();
assert_eq!(key, round_key);
assert_eq!(item.to_string(), round_item.to_string());
}
}