Skip to content

Commit

Permalink
Merge #214
Browse files Browse the repository at this point in the history
214: Allow simultaneous specification of both version and path r=ordian a=dherman

Closes #213.

Co-authored-by: David Herman <[email protected]>
  • Loading branch information
bors[bot] and dherman committed Jun 9, 2018
2 parents cc5b7a5 + 0cdc5f7 commit b111ed8
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 39 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,16 @@ $ # Add a non-crates.io crate
$ cargo add local_experiment --path=lib/trial-and-error/
$ # Add a non-crates.io crate; the crate name will be found automatically
$ cargo add lib/trial-and-error/
$ # Add a crates.io crate with a local development path
$ cargo add my_helper --vers=1.3.1 --path=lib/my-helper/
```

#### Usage

```plain
$ cargo add --help
Usage:
cargo add <crate> [--dev|--build|--optional] [--vers=<ver>|--git=<uri>|--path=<uri>] [options]
cargo add <crate> [--dev|--build|--optional] [options]
cargo add <crates>... [--dev|--build|--optional] [options]
cargo add (-h|--help)
cargo add --version
Expand All @@ -69,7 +71,8 @@ Specify what crate to add:
--vers <ver> Specify the version to grab from the registry (crates.io).
You can also specify versions as part of the name, e.g
`cargo add [email protected]`.
--git <uri> Specify a git repository to download the crate from.
--git <uri> Specify a git repository to download the crate from. This does not work
if either a version or path (or both) is specified.
--path <uri> Specify the path the crate should be loaded from.
Specify where to add the crate:
Expand Down
66 changes: 46 additions & 20 deletions src/bin/add/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ pub struct Args {
pub flag_quiet: bool,
}

fn parse_version_req(s: &str) -> Result<&str> {
semver::VersionReq::parse(s).chain_err(|| "Invalid dependency version requirement")?;
Ok(s)
}

impl Args {
/// Get dependency section
pub fn get_section(&self) -> Vec<String> {
Expand Down Expand Up @@ -80,29 +85,50 @@ impl Args {

let crate_name = CrateName::new(&self.arg_crate);

let dependency = if let Some(krate) = crate_name.parse_as_version()? {
krate
let dependency = if let Some(dependency) = crate_name.parse_as_version()? {
if let Some(ref url) = self.flag_git {
let url = url.clone();
let version = dependency.version().unwrap().to_string();
Err(ErrorKind::GitUrlWithVersion(url, version))?;
}
if let Some(ref path) = self.flag_path {
dependency.set_path(path.to_str().unwrap())
} else {
dependency
}
} else if !crate_name.is_url_or_path() {
let dependency = Dependency::new(&self.arg_crate);

if let Some(ref version) = self.flag_vers {
semver::VersionReq::parse(version)
.chain_err(|| "Invalid dependency version requirement")?;
dependency.set_version(version)
} else if let Some(ref repo) = self.flag_git {
dependency.set_git(repo)
} else if let Some(ref path) = self.flag_path {
dependency.set_path(path.to_str().unwrap())
} else {
let dep = get_latest_dependency(&self.arg_crate, self.flag_allow_prerelease)?;
let v = format!(
"{prefix}{version}",
prefix = self.get_upgrade_prefix().unwrap_or(""),
// If version is unavailable `get_latest_dependency` must have
// returned `Err(FetchVersionError::GetVersion)`
version = dep.version().unwrap_or_else(|| unreachable!())
);
dep.set_version(&v)
match (&self.flag_git, &self.flag_vers, &self.flag_path) {
(&Some(ref repo), &Some(ref version), _) => {
let repo = repo.clone();
let version = version.to_string();
return Err(ErrorKind::GitUrlWithVersion(repo, version))?;
}
(&Some(ref repo), &None, &Some(ref path)) => {
let repo = repo.clone();
let path = path.to_string_lossy().to_string();
return Err(ErrorKind::GitUrlWithPath(repo, path))?;
}
(&Some(ref repo), &None, &None) => dependency.set_git(repo),
(&None, &Some(ref version), &Some(ref path)) => dependency
.set_version(parse_version_req(version)?)
.set_path(path.to_str().unwrap()),
(&None, &Some(ref version), &None) => {
dependency.set_version(parse_version_req(version)?)
}
(&None, &None, &Some(ref path)) => dependency.set_path(path.to_str().unwrap()),
(&None, &None, &None) => {
let dep = get_latest_dependency(&self.arg_crate, self.flag_allow_prerelease)?;
let v = format!(
"{prefix}{version}",
prefix = self.get_upgrade_prefix().unwrap_or(""),
// If version is unavailable `get_latest_dependency` must have
// returned `Err(FetchVersionError::GetVersion)`
version = dep.version().unwrap_or_else(|| unreachable!())
);
dep.set_version(&v)
}
}
} else {
crate_name.parse_crate_name_from_uri()?
Expand Down
17 changes: 15 additions & 2 deletions src/bin/add/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ use args::Args;

mod errors {
error_chain!{
errors {
/// Specified a dependency with both a git URL and a version.
GitUrlWithVersion(git: String, version: String) {
description("Specified git URL with version")
display("Cannot specify a git URL (`{}`) with a version (`{}`).", git, version)
}
/// Specified a dependency with both a git URL and a path.
GitUrlWithPath(git: String, path: String) {
description("Specified git URL with path")
display("Cannot specify a git URL (`{}`) with a path (`{}`).", git, path)
}
}
links {
CargoEditLib(::cargo_edit::Error, ::cargo_edit::ErrorKind);
}
Expand All @@ -38,7 +50,7 @@ use errors::*;

static USAGE: &'static str = r#"
Usage:
cargo add <crate> [--dev|--build|--optional] [--vers=<ver>|--git=<uri>|--path=<uri>] [options]
cargo add <crate> [--dev|--build|--optional] [options]
cargo add <crates>... [--dev|--build|--optional] [options]
cargo add (-h|--help)
cargo add --version
Expand All @@ -47,7 +59,8 @@ Specify what crate to add:
--vers <ver> Specify the version to grab from the registry (crates.io).
You can also specify versions as part of the name, e.g
`cargo add [email protected]`.
--git <uri> Specify a git repository to download the crate from.
--git <uri> Specify a git repository to download the crate from. This does not work
if either a version or path (or both) is specified.
--path <uri> Specify the path the crate should be loaded from.
Specify where to add the crate:
Expand Down
57 changes: 45 additions & 12 deletions src/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ use toml_edit;

#[derive(Debug, Hash, PartialEq, Eq, Clone)]
enum DependencySource {
Version(String),
Version {
version: Option<String>,
path: Option<String>,
},
Git(String),
Path(String),
}

/// A dependency handled by Cargo
Expand All @@ -21,7 +23,10 @@ impl Default for Dependency {
Dependency {
name: "".into(),
optional: false,
source: DependencySource::Version("0.1.0".into()),
source: DependencySource::Version {
version: None,
path: None,
},
}
}
}
Expand All @@ -37,7 +42,15 @@ impl Dependency {

/// Set dependency to a given version
pub fn set_version(mut self, version: &str) -> Dependency {
self.source = DependencySource::Version(version.into());
let old_source = self.source;
let old_path = match old_source {
DependencySource::Version { path, .. } => path,
_ => None,
};
self.source = DependencySource::Version {
version: Some(version.into()),
path: old_path,
};
self
}

Expand All @@ -49,7 +62,15 @@ impl Dependency {

/// Set dependency to a given path
pub fn set_path(mut self, path: &str) -> Dependency {
self.source = DependencySource::Path(path.into());
let old_source = self.source;
let old_version = match old_source {
DependencySource::Version { version, .. } => version,
_ => None,
};
self.source = DependencySource::Version {
version: old_version,
path: Some(path.into()),
};
self
}

Expand All @@ -61,7 +82,11 @@ impl Dependency {

/// Get version of dependency
pub fn version(&self) -> Option<&str> {
if let DependencySource::Version(ref version) = self.source {
if let DependencySource::Version {
version: Some(ref version),
..
} = self.source
{
Some(version)
} else {
None
Expand All @@ -76,21 +101,29 @@ impl Dependency {
pub fn to_toml(&self) -> (String, toml_edit::Item) {
let data: toml_edit::Item = match (self.optional, self.source.clone()) {
// Extra short when version flag only
(false, DependencySource::Version(v)) => toml_edit::value(v),
(
false,
DependencySource::Version {
version: Some(v),
path: None,
},
) => toml_edit::value(v),
// Other cases are represented as an inline table
(optional, source) => {
let mut data = toml_edit::InlineTable::default();

match source {
DependencySource::Version(v) => {
data.get_or_insert("version", v);
DependencySource::Version { version, path } => {
if let Some(v) = version {
data.get_or_insert("version", v);
}
if let Some(p) = path {
data.get_or_insert("path", p);
}
}
DependencySource::Git(v) => {
data.get_or_insert("git", v);
}
DependencySource::Path(v) => {
data.get_or_insert("path", v);
}
}
if self.optional {
data.get_or_insert("optional", optional);
Expand Down
113 changes: 110 additions & 3 deletions tests/cargo-add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,13 +419,120 @@ fn adds_local_source_without_flag() {
}

#[test]
fn package_kinds_are_mutually_exclusive() {
fn adds_local_source_with_version_flag() {
let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample");

// dependency not present beforehand
let toml = get_toml(&manifest);
assert!(toml["dependencies"].is_none());

execute_command(
&["add", "local", "--vers", "0.4.3", "--path", "/path/to/pkg"],
&manifest,
);

let toml = get_toml(&manifest);
let val = &toml["dependencies"]["local"];
assert_eq!(val["path"].as_str(), Some("/path/to/pkg"));
assert_eq!(val["version"].as_str(), Some("0.4.3"));

// check this works with other flags (e.g. --dev) as well
let toml = get_toml(&manifest);
assert!(toml["dev-dependencies"].is_none());

execute_command(
&[
"add",
"local-dev",
"--vers",
"0.4.3",
"--path",
"/path/to/pkg-dev",
"--dev",
],
&manifest,
);

let toml = get_toml(&manifest);
let val = &toml["dev-dependencies"]["local-dev"];
assert_eq!(val["path"].as_str(), Some("/path/to/pkg-dev"));
assert_eq!(val["version"].as_str(), Some("0.4.3"));
}

#[test]
fn adds_local_source_with_inline_version_notation() {
let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample");

// dependency not present beforehand
let toml = get_toml(&manifest);
assert!(toml["dependencies"].is_none());

execute_command(&["add", "[email protected]", "--path", "/path/to/pkg"], &manifest);

let toml = get_toml(&manifest);
let val = &toml["dependencies"]["local"];
assert_eq!(val["path"].as_str(), Some("/path/to/pkg"));
assert_eq!(val["version"].as_str(), Some("0.4.3"));

// check this works with other flags (e.g. --dev) as well
let toml = get_toml(&manifest);
assert!(toml["dev-dependencies"].is_none());

execute_command(
&[
"add",
"[email protected]",
"--path",
"/path/to/pkg-dev",
"--dev",
],
&manifest,
);

let toml = get_toml(&manifest);
let val = &toml["dev-dependencies"]["local-dev"];
assert_eq!(val["path"].as_str(), Some("/path/to/pkg-dev"));
assert_eq!(val["version"].as_str(), Some("0.4.3"));
}

#[test]
fn git_and_version_flags_are_mutually_exclusive() {
let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample");

let call = process::Command::new("target/debug/cargo-add")
.args(&["add", BOGUS_CRATE_NAME])
.args(&["--vers", "0.4.3"])
.args(&["--git", "git://git.git"])
.arg(format!("--manifest-path={}", &manifest))
.output()
.unwrap();

assert!(!call.status.success());
assert!(no_manifest_failures(&get_toml(&manifest).root));
}

#[test]
fn git_flag_and_inline_version_are_mutually_exclusive() {
let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample");

let call = process::Command::new("target/debug/cargo-add")
.args(&["add", &format!("{}@0.4.3", BOGUS_CRATE_NAME)])
.args(&["--git", "git://git.git"])
.arg(format!("--manifest-path={}", &manifest))
.output()
.unwrap();

assert!(!call.status.success());
assert!(no_manifest_failures(&get_toml(&manifest).root));
}

#[test]
fn git_and_path_are_mutually_exclusive() {
let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample");

let call = process::Command::new("target/debug/cargo-add")
.args(&["add", BOGUS_CRATE_NAME])
.args(&["--git", "git://git.git"])
.args(&["--path", "/path/here"])
.arg(format!("--manifest-path={}", &manifest))
.output()
Expand Down Expand Up @@ -730,7 +837,7 @@ fn no_argument() {
r"Invalid arguments.
Usage:
cargo add <crate> [--dev|--build|--optional] [--vers=<ver>|--git=<uri>|--path=<uri>] [options]
cargo add <crate> [--dev|--build|--optional] [options]
cargo add <crates>... [--dev|--build|--optional] [options]
cargo add (-h|--help)
cargo add --version",
Expand All @@ -746,7 +853,7 @@ fn unknown_flags() {
r"Unknown flag: '--flag'
Usage:
cargo add <crate> [--dev|--build|--optional] [--vers=<ver>|--git=<uri>|--path=<uri>] [options]
cargo add <crate> [--dev|--build|--optional] [options]
cargo add <crates>... [--dev|--build|--optional] [options]
cargo add (-h|--help)
cargo add --version",
Expand Down

0 comments on commit b111ed8

Please sign in to comment.