Skip to content

Commit

Permalink
Auto merge of #89 - waynenilsen:feat-upgrade-flag, r=killercup
Browse files Browse the repository at this point in the history
Upgrade flag another try at #61

I am working on a second try at #61 and wanted to submit a work in progress checkpoint to see if you're on board with the method for implementing the feature. I initially wanted to have docopt parse to a rust enum but couldn't quite figure that out so I figured that we could just handle invalid parameters sent to this flag with a panic.

The code is different enough that I figured I would just start over from latest master. What do you think so far?
  • Loading branch information
homu committed Jul 15, 2016
2 parents deef08f + 5141c7d commit cac61b1
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 196 deletions.
71 changes: 45 additions & 26 deletions src/bin/add/args.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
///! Handle `cargo add` arguments
/// ! Handle `cargo add` arguments
use semver;
use std::error::Error;
use cargo_edit::Dependency;
Expand Down Expand Up @@ -40,6 +40,8 @@ pub struct Args {
pub flag_manifest_path: Option<String>,
/// `--version`
pub flag_version: bool,
/// `---upgrade`
pub flag_upgrade: Option<String>,
}

impl Args {
Expand Down Expand Up @@ -67,12 +69,12 @@ impl Args {
let mut result = Vec::<Dependency>::new();
for arg_crate in &self.arg_crates {
let le_crate = if crate_name_has_version(&arg_crate) {
try!(parse_crate_name_with_version(arg_crate))
} else {
let v = try!(get_latest_version(&self.arg_crate));
Dependency::new(arg_crate).set_version(&v)
}
.set_optional(self.flag_optional);
try!(parse_crate_name_with_version(arg_crate))
} else {
let v = try!(get_latest_version(&self.arg_crate));
Dependency::new(arg_crate).set_version(&v)
}
.set_optional(self.flag_optional);

result.push(le_crate);
}
Expand All @@ -86,26 +88,42 @@ impl Args {


let dependency = if !crate_name_is_url_or_path(&self.arg_crate) {
let dependency = Dependency::new(&self.arg_crate);

if let Some(ref version) = self.flag_vers {
try!(semver::VersionReq::parse(&version));
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)
} else {
let v = try!(get_latest_version(&self.arg_crate));
dependency.set_version(&v)
}
} else {
try!(parse_crate_name_from_uri(&self.arg_crate))
}
.set_optional(self.flag_optional);
let dependency = Dependency::new(&self.arg_crate);

if let Some(ref version) = self.flag_vers {
try!(semver::VersionReq::parse(&version));
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)
} else {
let v = format!("{prefix}{version}",
prefix = self.get_upgrade_prefix().unwrap_or(""),
version = try!(get_latest_version(&self.arg_crate)));
dependency.set_version(&v)
}
} else {
try!(parse_crate_name_from_uri(&self.arg_crate))
}
.set_optional(self.flag_optional);

Ok(vec![dependency])
}

fn get_upgrade_prefix(&self) -> Option<&'static str> {
self.flag_upgrade.clone().and_then(|flag| match flag.to_uppercase().as_ref() {
"NONE" => Some("="),
"PATCH" => Some("~"),
"MINOR" => Some("^"),
"ALL" => Some(">="),
_ => {
println!("WARN: cannot understand upgrade option \"{}\", using default",
flag);
None
}
})
}
}

impl Default for Args {
Expand All @@ -122,6 +140,7 @@ impl Default for Args {
flag_optional: false,
flag_manifest_path: None,
flag_version: false,
flag_upgrade: None,
}
}
}
Expand Down Expand Up @@ -198,12 +217,12 @@ mod tests {
let github_url = "https://github.com/killercup/cargo-edit/";
let args_github = Args { arg_crate: github_url.to_owned(), ..Args::default() };
assert_eq!(args_github.parse_dependencies().unwrap(),
vec![Dependency::new("cargo-edit").set_git(github_url)]);
vec![Dependency::new("cargo-edit").set_git(github_url)]);

let gitlab_url = "https://gitlab.com/Polly-lang/Polly.git";
let args_gitlab = Args { arg_crate: gitlab_url.to_owned(), ..Args::default() };
assert_eq!(args_gitlab.parse_dependencies().unwrap(),
vec![Dependency::new("polly").set_git(gitlab_url)]);
vec![Dependency::new("polly").set_git(gitlab_url)]);
}

#[test]
Expand Down
102 changes: 52 additions & 50 deletions src/bin/add/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ pub fn get_latest_version(crate_name: &str) -> Result<String, FetchVersionError>
let crate_json = try!(Json::from_str(&crate_data));

crate_json.as_object()
.and_then(|c| c.get("crate"))
.and_then(|c| c.as_object())
.and_then(|c| c.get("max_version"))
.and_then(|v| v.as_string())
.map(|v| v.to_owned())
.ok_or(FetchVersionError::GetVersion)
.and_then(|c| c.get("crate"))
.and_then(|c| c.as_object())
.and_then(|c| c.get("max_version"))
.and_then(|v| v.as_string())
.map(|v| v.to_owned())
.ok_or(FetchVersionError::GetVersion)
}

quick_error! {
Expand Down Expand Up @@ -64,9 +64,9 @@ quick_error! {
fn fetch_cratesio(path: &str) -> Result<String, FetchVersionError> {
let mut http_handle = http::Handle::new();
let req = Request::new(&mut http_handle, Method::Get)
.uri(format!("{}/api/v1{}", REGISTRY_HOST, path))
.header("Accept", "application/json")
.content_type("application/json");
.uri(format!("{}/api/v1{}", REGISTRY_HOST, path))
.header("Accept", "application/json")
.content_type("application/json");
handle_request(req.exec()).map_err(From::from)
}

Expand Down Expand Up @@ -139,26 +139,27 @@ quick_error! {
/// - the response from github is an error or in an incorrect format.
pub fn get_crate_name_from_github(repo: &str) -> Result<String, FetchGitError> {
let re = Regex::new(r"^https://github.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$")
.unwrap();
.unwrap();

re.captures(repo)
.ok_or(FetchGitError::ParseRegex)
.and_then(|cap| {
match (cap.at(1), cap.at(2)) {
(Some(ref user), Some(ref repo)) => {
let url = format!("https://raw.githubusercontent.com/{}/{}/master/Cargo.toml",
user,
repo);

let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url).and_then(|m| {
m.parse()
.map_err(|_| FetchGitError::ParseCargoToml)
});
data.and_then(|ref manifest| get_name_from_manifest(manifest))
}
_ => Err(FetchGitError::IncompleteCaptures),
}
})
.ok_or(FetchGitError::ParseRegex)
.and_then(|cap| {
match (cap.at(1), cap.at(2)) {
(Some(ref user), Some(ref repo)) => {
let url = format!("https://raw.githubusercontent.com/{}/{}/master/Cargo.toml",
user,
repo);

let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url)
.and_then(|m| {
m.parse()
.map_err(|_| FetchGitError::ParseCargoToml)
});
data.and_then(|ref manifest| get_name_from_manifest(manifest))
}
_ => Err(FetchGitError::IncompleteCaptures),
}
})
}

/// Query crate name by accessing a gitlab repo Cargo.toml
Expand All @@ -170,24 +171,25 @@ pub fn get_crate_name_from_github(repo: &str) -> Result<String, FetchGitError> {
/// - the response from gitlab is an error or in an incorrect format.
pub fn get_crate_name_from_gitlab(repo: &str) -> Result<String, FetchGitError> {
let re = Regex::new(r"^https://gitlab.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$")
.unwrap();
.unwrap();

re.captures(repo)
.ok_or(FetchGitError::ParseRegex)
.and_then(|cap| {
match (cap.at(1), cap.at(2)) {
(Some(ref user), Some(ref repo)) => {
let url = format!("https://gitlab.com/{}/{}/raw/master/Cargo.toml", user, repo);

let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url).and_then(|m| {
m.parse()
.map_err(|_| FetchGitError::ParseCargoToml)
});
data.and_then(|ref manifest| get_name_from_manifest(manifest))
}
_ => Err(FetchGitError::IncompleteCaptures),
}
})
.ok_or(FetchGitError::ParseRegex)
.and_then(|cap| {
match (cap.at(1), cap.at(2)) {
(Some(ref user), Some(ref repo)) => {
let url = format!("https://gitlab.com/{}/{}/raw/master/Cargo.toml", user, repo);

let data: Result<Manifest, _> = get_cargo_toml_from_git_url(&url)
.and_then(|m| {
m.parse()
.map_err(|_| FetchGitError::ParseCargoToml)
});
data.and_then(|ref manifest| get_name_from_manifest(manifest))
}
_ => Err(FetchGitError::IncompleteCaptures),
}
})
}

/// Query crate name by accessing Cargo.toml in a local path
Expand All @@ -203,17 +205,17 @@ pub fn get_crate_name_from_path(path: &str) -> Result<String, FetchGitError> {

fn get_name_from_manifest(manifest: &Manifest) -> Result<String, FetchGitError> {
manifest.data
.get("package")
.and_then(|m| m.lookup("name"))
.and_then(|name| name.as_str().map(|s| s.to_string()))
.ok_or(FetchGitError::ParseCargoToml)
.get("package")
.and_then(|m| m.lookup("name"))
.and_then(|name| name.as_str().map(|s| s.to_string()))
.ok_or(FetchGitError::ParseCargoToml)
}

fn get_cargo_toml_from_git_url(url: &str) -> Result<String, FetchGitError> {
let mut http_handle = http::Handle::new();
let req = Request::new(&mut http_handle, Method::Get)
.uri(url)
.header("Accept", "text/plain")
.content_type("text/plain");
.uri(url)
.header("Accept", "text/plain")
.content_type("text/plain");
handle_request(req.exec()).map_err(From::from)
}
6 changes: 4 additions & 2 deletions src/bin/add/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Options:
--manifest-path=<path> Path to the manifest to add a dependency to.
-h --help Show this help page.
--version Show version.
--upgrade=<method> Chose method of semantic version upgrade. Must be one of
none, patch, minor or all.
This command allows you to add a dependency to a Cargo.toml manifest file. If <crate> is a github
or gitlab repository URL, or a local path, `cargo add` will try to automatically get the crate name
Expand Down Expand Up @@ -80,8 +82,8 @@ fn handle_add(args: &Args) -> Result<(), Box<Error>> {

fn main() {
let args = docopt::Docopt::new(USAGE)
.and_then(|d| d.decode::<Args>())
.unwrap_or_else(|err| err.exit());
.and_then(|d| d.decode::<Args>())
.unwrap_or_else(|err| err.exit());

if args.flag_version {
println!("cargo-add version {}", env!("CARGO_PKG_VERSION"));
Expand Down
22 changes: 11 additions & 11 deletions src/bin/list/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ pub fn list_section(manifest: &Manifest, section: &str) -> Result<String, ListEr
let mut output = vec![];

let list = try!(manifest.data
.get(section)
.and_then(|field| field.as_table())
.ok_or_else(|| ListError::SectionMissing(String::from(section))));
.get(section)
.and_then(|field| field.as_table())
.ok_or_else(|| ListError::SectionMissing(String::from(section))));

let name_max_len = list.keys().map(|k| k.len()).max().unwrap_or(0);

Expand All @@ -21,25 +21,25 @@ pub fn list_section(manifest: &Manifest, section: &str) -> Result<String, ListEr
toml::Value::String(ref version) => version.clone(),
toml::Value::Table(_) => {
try!(val.lookup("version")
.and_then(|field| field.as_str().map(|s| s.to_owned()))
.or_else(|| val.lookup("git").map(|repo| format!("git: {}", repo)))
.or_else(|| val.lookup("path").map(|path| format!("path: {}", path)))
.ok_or_else(|| ListError::VersionMissing(name.clone(), section.to_owned())))
.and_then(|field| field.as_str().map(|s| s.to_owned()))
.or_else(|| val.lookup("git").map(|repo| format!("git: {}", repo)))
.or_else(|| val.lookup("path").map(|path| format!("path: {}", path)))
.ok_or_else(|| ListError::VersionMissing(name.clone(), section.to_owned())))
}
_ => String::from(""),
};

let optional = if let toml::Value::Table(_) = *val {
val.lookup("optional")
.and_then(|field| field.as_bool())
.unwrap_or(false)
.and_then(|field| field.as_bool())
.unwrap_or(false)
} else {
false
};

output.push(format!("{name} {version}{optional}",
name = name.pad_to_width_with_alignment(name_max_len,
Alignment::Left),
name =
name.pad_to_width_with_alignment(name_max_len, Alignment::Left),
version = version,
optional = if optional {
" (optional)"
Expand Down
23 changes: 11 additions & 12 deletions src/bin/list/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,24 @@ impl Args {

fn handle_list(args: &Args) -> Result<String, Box<Error>> {
if args.flag_tree {
let manifest = try!(Manifest::open_lock_file(&args.flag_manifest_path
.as_ref()
.map(|s| &s[..])));
list_tree(&manifest)
} else {
let manifest = try!(Manifest::open(&args.flag_manifest_path.as_ref().map(|s| &s[..])));
list_section(&manifest, args.get_section())
.or_else(|err| match err {
let manifest = try!(Manifest::open_lock_file(&args.flag_manifest_path
.as_ref()
.map(|s| &s[..])));
list_tree(&manifest)
} else {
let manifest = try!(Manifest::open(&args.flag_manifest_path.as_ref().map(|s| &s[..])));
list_section(&manifest, args.get_section()).or_else(|err| match err {
ListError::SectionMissing(..) => Ok("".into()),
_ => Err(err),
})
}
.map_err(From::from)
}
.map_err(From::from)
}

fn main() {
let args = docopt::Docopt::new(USAGE)
.and_then(|d| d.decode::<Args>())
.unwrap_or_else(|err| err.exit());
.and_then(|d| d.decode::<Args>())
.unwrap_or_else(|err| err.exit());

if args.flag_version {
println!("cargo-list version {}", env!("CARGO_PKG_VERSION"));
Expand Down
Loading

0 comments on commit cac61b1

Please sign in to comment.