Merge #297
297: uses clap for the CLI r=kbknapp a=kbknapp
Co-authored-by: Kevin K <kbknapp@gmail.com>
diff --git a/Cargo.lock b/Cargo.lock
index b937461..886fb82 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -152,7 +152,7 @@
dependencies = [
"anyhow",
"cargo",
- "docopt",
+ "clap",
"env_logger",
"git2-curl",
"semver",
@@ -220,7 +220,7 @@
"ansi_term",
"atty",
"bitflags",
- "strsim 0.8.0",
+ "strsim",
"textwrap",
"unicode-width",
"vec_map",
@@ -337,18 +337,6 @@
]
[[package]]
-name = "docopt"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f3f119846c823f9eafcf953a8f6ffb6ed69bf6240883261a7f13b634579a51f"
-dependencies = [
- "lazy_static",
- "regex",
- "serde",
- "strsim 0.10.0",
-]
-
-[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1036,12 +1024,6 @@
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
-name = "strsim"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
-
-[[package]]
name = "syn"
version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 1ac8fde..305ff4e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,7 @@
"Ricky H. <ricky.hosfelt@gmail.com>",
]
categories = ["development-tools", "development-tools::cargo-plugins"]
-edition = "2018"
+edition = "2021"
exclude = ["*.png"]
keywords = [
"cargo",
@@ -30,7 +30,6 @@
[dependencies]
anyhow = "1.0"
cargo = "0.57.0"
-docopt = "1.1.0"
env_logger = "0.9.0"
git2-curl = "0.14.0"
semver = "1.0.0"
@@ -40,6 +39,7 @@
tabwriter = "1.2.1"
tempfile = "3"
toml = "~0.5.0"
+clap = "2.33.3"
[dependencies.termcolor]
optional = true
diff --git a/src/cargo_ops/elaborate_workspace.rs b/src/cargo_ops/elaborate_workspace.rs
index e6b37e4..2f1f016 100644
--- a/src/cargo_ops/elaborate_workspace.rs
+++ b/src/cargo_ops/elaborate_workspace.rs
@@ -66,7 +66,7 @@
) -> CargoResult<ElaborateWorkspace<'ela>> {
// new in cargo 0.54.0
let flag_features: BTreeSet<FeatureValue> = options
- .flag_features
+ .features
.iter()
.map(|feature| FeatureValue::new(InternedString::from(feature)))
.collect();
@@ -118,13 +118,13 @@
pkgs,
pkg_deps,
pkg_status: RefCell::new(HashMap::new()),
- workspace_mode: options.flag_workspace || workspace.current().is_err(),
+ workspace_mode: options.workspace || workspace.current().is_err(),
})
}
/// Determine root package based on current workspace and CLI options
pub fn determine_root(&self, options: &Options) -> CargoResult<PackageId> {
- if let Some(ref root_name) = options.flag_root {
+ if let Some(ref root_name) = options.root {
if let Ok(workspace_root) = self.workspace.current() {
if root_name == workspace_root.name().as_str() {
Ok(workspace_root.package_id())
@@ -239,7 +239,7 @@
self.pkg_status.borrow_mut().insert(path.clone(), status);
// next layer
// this unwrap is safe since we first check if it is None :)
- if options.flag_depth.is_none() || depth < options.flag_depth.unwrap() {
+ if options.depth.is_none() || depth < options.depth.unwrap() {
self.pkg_deps[pkg]
.keys()
.filter(|dep| !path.contains(dep))
@@ -279,7 +279,7 @@
let pkg = path.last().ok_or(OutdatedError::EmptyPath)?;
let name = pkg.name().to_string();
- if options.flag_ignore.contains(&name) {
+ if options.ignore.contains(&name) {
continue;
}
@@ -287,7 +287,7 @@
// generate lines
let status = &self.pkg_status.borrow_mut()[&path];
if (status.compat.is_changed() || status.latest.is_changed())
- && (options.flag_packages.is_empty() || options.flag_packages.contains(&name))
+ && (options.packages.is_empty() || options.packages.contains(&name))
{
// name version compatible latest kind platform
let parent = path.get(path.len() - 2);
@@ -326,7 +326,7 @@
}
// next layer
// this unwrap is safe since we first check if it is None :)
- if options.flag_depth.is_none() || depth < options.flag_depth.unwrap() {
+ if options.depth.is_none() || depth < options.depth.unwrap() {
self.pkg_deps[pkg]
.keys()
.filter(|dep| !path.contains(dep))
@@ -379,7 +379,7 @@
let pkg = path.last().ok_or(OutdatedError::EmptyPath)?;
let name = pkg.name().to_string();
- if options.flag_ignore.contains(&name) {
+ if options.ignore.contains(&name) {
continue;
}
@@ -387,7 +387,7 @@
// generate lines
let status = &self.pkg_status.borrow_mut()[&path];
if (status.compat.is_changed() || status.latest.is_changed())
- && (options.flag_packages.is_empty() || options.flag_packages.contains(&name))
+ && (options.packages.is_empty() || options.packages.contains(&name))
{
// name version compatible latest kind platform
// safely get the parent index
@@ -438,7 +438,7 @@
}
// next layer
// this unwrap is safe since we first check if it is None :)
- if options.flag_depth.is_none() || depth < options.flag_depth.unwrap() {
+ if options.depth.is_none() || depth < options.depth.unwrap() {
self.pkg_deps[pkg]
.keys()
.filter(|dep| !path.contains(dep))
diff --git a/src/cargo_ops/temp_project.rs b/src/cargo_ops/temp_project.rs
index 38acad2..826e89e 100644
--- a/src/cargo_ops/temp_project.rs
+++ b/src/cargo_ops/temp_project.rs
@@ -189,11 +189,11 @@
let mut config = Config::new(shell, cwd, homedir);
config.configure(
0,
- options.flag_verbose == 0,
- options.flag_color.as_deref(),
+ options.verbose == 0,
+ Some(&options.color.to_string().to_ascii_lowercase()),
options.frozen(),
options.locked(),
- options.flag_offline,
+ options.offline,
&cargo_home_path,
&[],
&[],
@@ -396,7 +396,7 @@
} else if find_latest {
// this unwrap is safe since we check if `version_req` is `None` before this
// (which is only `None` if `requirement` is `None`)
- self.options.flag_aggressive
+ self.options.aggressive
|| valid_latest_version(requirement.unwrap(), summary.version())
} else {
// this unwrap is safe since we check if `version_req` is `None` before this
@@ -436,12 +436,7 @@
if self.options.all_features() {
return true;
}
- if !optional
- && self
- .options
- .flag_features
- .contains(&String::from("default"))
- {
+ if !optional && self.options.features.contains(&String::from("default")) {
return true;
}
let features_table = match *features_table {
@@ -450,7 +445,7 @@
};
let mut to_resolve: Vec<&str> = self
.options
- .flag_features
+ .features
.iter()
.filter(|f| !f.is_empty())
.map(String::as_str)
@@ -495,7 +490,7 @@
// In short this allows cargo to build the package with semver minor compatibilities issues
// https://github.com/rust-lang/cargo/issues/6584
// https://github.com/kbknapp/cargo-outdated/issues/230
- if self.options.flag_exclude.contains(&dep_key) {
+ if self.options.exclude.contains(&dep_key) {
continue;
}
@@ -682,13 +677,11 @@
fn warn<T: ::std::fmt::Display>(&self, message: T) -> CargoResult<()> {
let original_verbosity = self.config.shell().verbosity();
- self.config
- .shell()
- .set_verbosity(if self.options.flag_quiet {
- Verbosity::Quiet
- } else {
- Verbosity::Normal
- });
+ self.config.shell().set_verbosity(if self.options.quiet {
+ Verbosity::Quiet
+ } else {
+ Verbosity::Normal
+ });
self.config.shell().warn(message)?;
self.config.shell().set_verbosity(original_verbosity);
Ok(())
diff --git a/src/cli.rs b/src/cli.rs
new file mode 100644
index 0000000..65df241
--- /dev/null
+++ b/src/cli.rs
@@ -0,0 +1,476 @@
+use clap::{
+ arg_enum, crate_version, value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches,
+ SubCommand,
+};
+
+arg_enum! {
+ #[derive(Copy, Clone, Debug, PartialEq)]
+ pub enum Format {
+ List,
+ Json,
+ }
+}
+
+impl Default for Format {
+ fn default() -> Self { Format::List }
+}
+
+arg_enum! {
+ #[derive(Copy, Clone, Debug, PartialEq)]
+ pub enum Color {
+ Auto,
+ Never,
+ Always
+ }
+}
+
+impl Default for Color {
+ fn default() -> Self { Color::Auto }
+}
+
+/// Options from CLI arguments
+#[derive(Debug, PartialEq, Default)]
+pub struct Options {
+ pub format: Format,
+ pub color: Color,
+ pub features: Vec<String>,
+ pub ignore: Vec<String>,
+ pub exclude: Vec<String>,
+ pub manifest_path: Option<String>,
+ pub quiet: bool,
+ pub verbose: u64,
+ pub exit_code: i32,
+ pub packages: Vec<String>,
+ pub root: Option<String>,
+ pub depth: Option<i32>,
+ pub root_deps_only: bool,
+ pub workspace: bool,
+ pub aggressive: bool,
+ pub offline: bool,
+}
+
+impl Options {
+ pub fn all_features(&self) -> bool { self.features.is_empty() }
+
+ pub fn no_default_features(&self) -> bool {
+ !(self.features.is_empty() || self.features.contains(&"default".to_owned()))
+ }
+
+ pub fn locked(&self) -> bool { false }
+
+ pub fn frozen(&self) -> bool { false }
+}
+
+impl<'a> From<&ArgMatches<'a>> for Options {
+ fn from(m: &ArgMatches<'a>) -> Self {
+ let mut opts = Options {
+ format: value_t_or_exit!(m.value_of("format"), Format),
+ color: value_t_or_exit!(m.value_of("color"), Color),
+ features: m
+ .values_of("features")
+ .map(|vals| {
+ vals.flat_map(|x| x.split_ascii_whitespace().collect::<Vec<_>>())
+ .map(ToOwned::to_owned)
+ .collect()
+ })
+ .unwrap_or_else(Vec::new),
+ ignore: m
+ .values_of("ignore")
+ .map(|vals| {
+ vals.flat_map(|x| x.split_ascii_whitespace().collect::<Vec<_>>())
+ .map(ToOwned::to_owned)
+ .collect()
+ })
+ .unwrap_or_else(Vec::new),
+ exclude: m
+ .values_of("exclude")
+ .map(|vals| {
+ vals.flat_map(|x| x.split_ascii_whitespace().collect::<Vec<_>>())
+ .map(ToOwned::to_owned)
+ .collect()
+ })
+ .unwrap_or_else(Vec::new),
+ manifest_path: m.value_of("manifest-path").map(ToOwned::to_owned),
+ quiet: m.is_present("quiet"),
+ verbose: m.occurrences_of("verbose"),
+ exit_code: value_t!(m, "exit-code", i32).ok().unwrap_or(0),
+ packages: m
+ .values_of("packages")
+ .map(|vals| {
+ vals.flat_map(|x| x.split_ascii_whitespace().collect::<Vec<_>>())
+ .map(ToOwned::to_owned)
+ .collect::<Vec<_>>()
+ })
+ .unwrap_or_else(Vec::new),
+ root: m.value_of("root").map(ToOwned::to_owned),
+ depth: value_t!(m, "depth", i32).ok(),
+ root_deps_only: m.is_present("root-deps-only"),
+ workspace: m.is_present("workspace"),
+ aggressive: m.is_present("aggressive"),
+ offline: m.is_present("offline"),
+ };
+
+ if m.is_present("root-deps-only") {
+ opts.depth = Some(1);
+ }
+
+ opts
+ }
+}
+
+fn build() -> App<'static, 'static> {
+ App::new("cargo-outdated")
+ .bin_name("cargo")
+ .setting(AppSettings::SubcommandRequired)
+ .subcommand(
+ SubCommand::with_name("outdated")
+ .setting(AppSettings::UnifiedHelpMessage)
+ .about("Displays information about project dependency versions")
+ .version(crate_version!())
+ .arg(
+ Arg::with_name("aggressive")
+ .short("a")
+ .long("aggresssive")
+ .help("Ignores channels for latest updates"),
+ )
+ .arg(
+ Arg::with_name("quiet")
+ .short("q")
+ .long("quiet")
+ .help("Suppresses warnings"),
+ )
+ .arg(
+ Arg::with_name("root-deps-only")
+ .short("R")
+ .long("root-deps-only")
+ .help("Only check root dependencies (Equivalent to --depth=1)"),
+ )
+ .arg(
+ Arg::with_name("workspace")
+ .short("w")
+ .long("workspace")
+ .help("Checks updates for all workspace members rather than only the root package"),
+ )
+ .arg(
+ Arg::with_name("offline")
+ .short("o")
+ .long("offline")
+ .help("Run without accessing the network (useful for testing w/ local registries)"),
+ )
+ .arg(
+ Arg::with_name("format")
+ .long("format")
+ .default_value("list")
+ .case_insensitive(true)
+ .possible_values(&Format::variants())
+ .value_name("FORMAT")
+ .help("Output formatting"),
+ )
+ .arg(
+ Arg::with_name("ignore")
+ .short("i")
+ .long("ignore")
+ .help("Dependencies to not print in the output (comma separated or one per '--ignore' argument)")
+ .value_delimiter(",")
+ .number_of_values(1)
+ .multiple(true)
+ .value_name("DEPENDENCIES"),
+ )
+ .arg(
+ Arg::with_name("exclude")
+ .short("x")
+ .long("exclude")
+ .help("Dependencies to exclude from building (comma separated or one per '--exclude' argument)")
+ .value_delimiter(",")
+ .multiple(true)
+ .number_of_values(1)
+ .value_name("DEPENDENCIES"),
+ )
+ .arg(
+ Arg::with_name("verbose")
+ .short("v")
+ .long("verbose")
+ .multiple(true)
+ .help("Use verbose output")
+ )
+ .arg(
+ Arg::with_name("color")
+ .long("color")
+ .possible_values(&Color::variants())
+ .default_value("auto")
+ .value_name("COLOR")
+ .case_insensitive(true)
+ .help("Output coloring")
+ )
+ .arg(
+ Arg::with_name("depth")
+ .short("d")
+ .long("depth")
+ .value_name("NUM")
+ .help("How deep in the dependency chain to search (Defaults to all dependencies when omitted)")
+ )
+ .arg(
+ Arg::with_name("exit-code")
+ .long("exit-code")
+ .help("The exit code to return on new versions found")
+ .default_value("0")
+ .value_name("NUM"))
+ .arg(
+ Arg::with_name("manifest-path")
+ .short("m")
+ .long("manifest-path")
+ .help("Path to the Cargo.toml file to use (Defaults to Cargo.toml in project root)")
+ .value_name("PATH"))
+ .arg(
+ Arg::with_name("root")
+ .short("r")
+ .long("root")
+ .help("Package to treat as the root package")
+ .value_name("ROOT"))
+ .arg(
+ Arg::with_name("packages")
+ .short("p")
+ .long("packages")
+ .help("Packages to inspect for updates (comma separated or one per '--packages' argument)")
+ .value_delimiter(",")
+ .number_of_values(1)
+ .multiple(true)
+ .value_name("PKGS"))
+ .arg(
+ Arg::with_name("features")
+ .long("features")
+ .value_delimiter(",")
+ .help("Space-separated list of features")
+ .multiple(true)
+ .number_of_values(1)
+ .value_name("FEATURES"))
+ )
+}
+
+pub fn parse() -> Options {
+ let matches = build().get_matches();
+
+ Options::from(matches.subcommand_matches("outdated").unwrap())
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ fn options(args: &[&str]) -> Options {
+ let mut argv = vec!["cargo", "outdated"];
+ argv.extend(args);
+ let m = build().get_matches_from(argv);
+ Options::from(m.subcommand_matches("outdated").unwrap())
+ }
+
+ fn options_fail(args: &[&str]) -> clap::Result<ArgMatches<'static>> {
+ let mut argv = vec!["cargo", "outdated"];
+ argv.extend(args);
+ build().get_matches_from_safe(argv)
+ }
+
+ #[test]
+ fn default() {
+ let opts = options(&[]);
+ assert_eq!(Options::default(), opts)
+ }
+
+ #[test]
+ fn root_only() {
+ let opts = options(&["--root-deps-only"]);
+ assert_eq!(
+ Options {
+ depth: Some(1),
+ root_deps_only: true,
+ ..Options::default()
+ },
+ opts
+ )
+ }
+
+ #[test]
+ fn features() {
+ let opts1 = options(&["--features=one,two,three"]);
+ let opts2 = options(&["--features", "one,two,three"]);
+ let opts3 = options(&["--features", "one two three"]);
+ let opts4 = options(&[
+ "--features",
+ "one",
+ "--features",
+ "two",
+ "--features",
+ "three",
+ ]);
+ let opts5 = options(&["--features", "one", "--features", "two,three"]);
+
+ let correct = Options {
+ features: vec!["one".into(), "two".into(), "three".into()],
+ ..Options::default()
+ };
+
+ assert_eq!(correct, opts1);
+ assert_eq!(correct, opts2);
+ assert_eq!(correct, opts3);
+ assert_eq!(correct, opts4);
+ assert_eq!(correct, opts5);
+ }
+
+ #[test]
+ fn features_fail() {
+ let res = options_fail(&["--features", "one", "two"]);
+ assert!(res.is_err());
+ assert_eq!(
+ res.as_ref().unwrap_err().kind,
+ clap::ErrorKind::UnknownArgument,
+ "{:?}",
+ res.as_ref().unwrap_err().kind
+ );
+ }
+
+ #[test]
+ fn exclude() {
+ let opts1 = options(&["--exclude=one,two,three"]);
+ let opts2 = options(&["--exclude", "one,two,three"]);
+ let opts3 = options(&["--exclude", "one two three"]);
+ let opts4 = options(&["--exclude", "one", "--exclude", "two", "--exclude", "three"]);
+ let opts5 = options(&["--exclude", "one", "--exclude", "two,three"]);
+ let correct = Options {
+ exclude: vec!["one".into(), "two".into(), "three".into()],
+ ..Options::default()
+ };
+
+ assert_eq!(correct, opts1);
+ assert_eq!(correct, opts2);
+ assert_eq!(correct, opts3);
+ assert_eq!(correct, opts4);
+ assert_eq!(correct, opts5);
+ }
+
+ #[test]
+ fn exclude_fail() {
+ let res = options_fail(&["--exclude", "one", "two"]);
+ assert!(res.is_err());
+ assert_eq!(
+ res.as_ref().unwrap_err().kind,
+ clap::ErrorKind::UnknownArgument,
+ "{:?}",
+ res.as_ref().unwrap_err().kind
+ );
+ }
+
+ #[test]
+ fn ignore() {
+ let opts1 = options(&["--ignore=one,two,three"]);
+ let opts2 = options(&["--ignore", "one,two,three"]);
+ let opts3 = options(&["--ignore", "one two three"]);
+ let opts4 = options(&["--ignore", "one", "--ignore", "two", "--ignore", "three"]);
+ let opts5 = options(&["--ignore", "one", "--ignore", "two,three"]);
+ let correct = Options {
+ ignore: vec!["one".into(), "two".into(), "three".into()],
+ ..Options::default()
+ };
+
+ assert_eq!(correct, opts1);
+ assert_eq!(correct, opts2);
+ assert_eq!(correct, opts3);
+ assert_eq!(correct, opts4);
+ assert_eq!(correct, opts5);
+ }
+
+ #[test]
+ fn ignore_fail() {
+ let res = options_fail(&["--ignore", "one", "two"]);
+ assert!(res.is_err());
+ assert_eq!(
+ res.as_ref().unwrap_err().kind,
+ clap::ErrorKind::UnknownArgument,
+ "{:?}",
+ res.as_ref().unwrap_err().kind
+ );
+ }
+
+ #[test]
+ fn verbose() {
+ let opts1 = options(&["--verbose", "--verbose", "--verbose"]);
+ let correct = Options {
+ verbose: 3,
+ ..Options::default()
+ };
+
+ assert_eq!(correct, opts1);
+ }
+
+ #[test]
+ fn packages() {
+ let opts1 = options(&["--packages", "one,two"]);
+ let opts2 = options(&["--packages", "one two"]);
+ let opts3 = options(&["--packages", "one", "--packages", "two"]);
+ let correct = Options {
+ packages: vec!["one".into(), "two".into()],
+ ..Options::default()
+ };
+
+ assert_eq!(correct, opts1);
+ assert_eq!(correct, opts2);
+ assert_eq!(correct, opts3);
+ }
+
+ #[test]
+ fn packages_fail() {
+ let res = options_fail(&["--packages", "one", "two"]);
+ assert!(res.is_err());
+ assert_eq!(
+ res.as_ref().unwrap_err().kind,
+ clap::ErrorKind::UnknownArgument,
+ "{:?}",
+ res.as_ref().unwrap_err().kind
+ );
+ }
+
+ #[test]
+ fn format_case() {
+ let opts1 = options(&["--format", "JsOn"]);
+ let correct = Options {
+ format: Format::Json,
+ ..Options::default()
+ };
+
+ assert_eq!(correct, opts1);
+ }
+
+ #[test]
+ fn format_unknown() {
+ let res = options_fail(&["--format", "foobar"]);
+ assert!(res.is_err());
+ assert_eq!(
+ res.as_ref().unwrap_err().kind,
+ clap::ErrorKind::InvalidValue,
+ "{:?}",
+ res.as_ref().unwrap_err().kind
+ );
+ }
+
+ #[test]
+ fn color_case() {
+ let opts1 = options(&["--color", "NeVeR"]);
+ let correct = Options {
+ color: Color::Never,
+ ..Options::default()
+ };
+
+ assert_eq!(correct, opts1);
+ }
+
+ #[test]
+ fn color_unknown() {
+ let res = options_fail(&["--color", "foobar"]);
+ assert!(res.is_err());
+ assert_eq!(
+ res.as_ref().unwrap_err().kind,
+ clap::ErrorKind::InvalidValue,
+ "{:?}",
+ res.as_ref().unwrap_err().kind
+ );
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 92e39ce..050c9f4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,114 +6,24 @@
#[macro_use]
mod macros;
mod cargo_ops;
+mod cli;
mod error;
-use crate::{
- cargo_ops::{ElaborateWorkspace, TempProject},
- error::OutdatedError,
-};
-
use cargo::core::shell::Verbosity;
use cargo::core::Workspace;
use cargo::ops::needs_custom_http_transport;
use cargo::util::important_paths::find_root_manifest_for_wd;
use cargo::util::{CargoResult, CliError, Config};
-use docopt::Docopt;
-/// usage message for --help
-pub const USAGE: &str = "
-Displays information about project dependency versions
-
-USAGE:
- cargo outdated [options]
-
-Options:
- -a, --aggressive Ignores channels for latest updates
- -h, --help Prints help information
- --format FORMAT Output formatting [default: list]
- [values: list, json]
- -i, --ignore DEPENDENCIES Comma separated list of dependencies to not print in the output
- -x, --exclude DEPENDENCIES Comma separated list of dependencies to exclude from building
- -q, --quiet Suppresses warnings
- -R, --root-deps-only Only check root dependencies (Equivalent to --depth=1)
- -V, --version Prints version information
- -v, --verbose ... Use verbose output
- -w, --workspace Checks updates for all workspace members rather than
- only the root package
- --color COLOR Coloring: auto, always, never [default: auto]
- [values: auto, always, never]
- -d, --depth NUM How deep in the dependency chain to search
- (Defaults to all dependencies when omitted)
- --exit-code NUM The exit code to return on new versions found [default: 0]
- --features FEATURES Space-separated list of features
- -m, --manifest-path FILE Path to the Cargo.toml file to use
- (Defaults to Cargo.toml in project root)
- -p, --packages PKGS Packages to inspect for updates
- -r, --root ROOT Package to treat as the root package
- -o, --offline Run without accessing the network (useful for testing w/ local registries)
-";
-
-/// Options from CLI arguments
-#[derive(serde_derive::Deserialize, Debug, PartialEq, Default)]
-pub struct Options {
- flag_format: Option<String>,
- flag_color: Option<String>,
- flag_features: Vec<String>,
- flag_ignore: Vec<String>,
- flag_exclude: Vec<String>,
- flag_manifest_path: Option<String>,
- flag_quiet: bool,
- flag_verbose: u32,
- flag_exit_code: i32,
- flag_packages: Vec<String>,
- flag_root: Option<String>,
- flag_depth: Option<i32>,
- flag_root_deps_only: bool,
- flag_workspace: bool,
- flag_aggressive: bool,
- flag_offline: bool,
-}
-
-impl Options {
- fn all_features(&self) -> bool { self.flag_features.is_empty() }
-
- fn no_default_features(&self) -> bool {
- !(self.flag_features.is_empty() || self.flag_features.contains(&"default".to_owned()))
- }
-
- fn locked(&self) -> bool { false }
-
- fn frozen(&self) -> bool { false }
-}
+use crate::{
+ cargo_ops::{ElaborateWorkspace, TempProject},
+ cli::{Format, Options},
+ error::OutdatedError,
+};
fn main() {
env_logger::init();
- let options = {
- let mut options: Options = Docopt::new(USAGE)
- .and_then(|d| {
- d.version(Some(
- concat!(env!("CARGO_PKG_NAME"), " v", env!("CARGO_PKG_VERSION")).to_owned(),
- ))
- .deserialize()
- })
- .unwrap_or_else(|e| e.exit());
- fn flat_split(arg: &[String]) -> Vec<String> {
- arg.iter()
- .flat_map(|s| s.split_whitespace())
- .flat_map(|s| s.split(','))
- .filter(|s| !s.is_empty())
- .map(ToString::to_string)
- .collect()
- }
- options.flag_features = flat_split(&options.flag_features);
- options.flag_ignore = flat_split(&options.flag_ignore);
- options.flag_exclude = flat_split(&options.flag_exclude);
- options.flag_packages = flat_split(&options.flag_packages);
- if options.flag_root_deps_only {
- options.flag_depth = Some(1);
- }
- options
- };
+ let options = cli::parse();
let mut config = match Config::default() {
Ok(cfg) => cfg,
@@ -137,7 +47,7 @@
}
}
- let exit_code = options.flag_exit_code;
+ let exit_code = options.exit_code;
let result = execute(options, &mut config);
match result {
Err(e) => {
@@ -165,12 +75,15 @@
config.nightly_features_allowed = true;
config.configure(
- options.flag_verbose,
- options.flag_quiet,
- options.flag_color.as_deref(),
+ options
+ .verbose
+ .try_into()
+ .expect("--verbose used too many times"),
+ options.quiet,
+ Some(&options.color.to_string().to_ascii_lowercase()),
options.frozen(),
options.locked(),
- options.flag_offline,
+ options.offline,
&cargo_home_path,
&[],
&[],
@@ -180,7 +93,7 @@
verbose!(config, "Parsing...", "current workspace");
// the Cargo.toml that we are actually working on
let mut manifest_abspath: std::path::PathBuf;
- let curr_manifest = if let Some(ref manifest_path) = options.flag_manifest_path {
+ let curr_manifest = if let Some(ref manifest_path) = options.manifest_path {
manifest_abspath = manifest_path.into();
if manifest_abspath.is_relative() {
verbose!(config, "Resolving...", "absolute path of manifest");
@@ -192,11 +105,11 @@
};
let curr_workspace = Workspace::new(&curr_manifest, config)?;
verbose!(config, "Resolving...", "current workspace");
- if options.flag_verbose == 0 {
+ if options.verbose == 0 {
config.shell().set_verbosity(Verbosity::Quiet);
}
let ela_curr = ElaborateWorkspace::from_workspace(&curr_workspace, &options)?;
- if options.flag_verbose > 0 {
+ if options.verbose > 0 {
config.shell().set_verbosity(Verbosity::Verbose);
} else {
config.shell().set_verbosity(Verbosity::Normal);
@@ -242,10 +155,9 @@
if ela_curr.workspace_mode {
let mut sum = 0;
- if options.flag_format == Some("list".to_string()) {
- verbose!(config, "Printing...", "Package status in list format");
- } else if options.flag_format == Some("json".to_string()) {
- verbose!(config, "Printing...", "Package status in json format");
+ match options.format {
+ Format::List => verbose!(config, "Printing...", "Package status in list format"),
+ Format::Json => verbose!(config, "Printing...", "Package status in json format"),
}
for member in ela_curr.workspace.members() {
@@ -256,10 +168,13 @@
config,
member.package_id(),
)?;
- if options.flag_format == Some("list".to_string()) {
- sum += ela_curr.print_list(&options, member.package_id(), sum > 0)?;
- } else if options.flag_format == Some("json".to_string()) {
- sum += ela_curr.print_json(&options, member.package_id())?;
+ match options.format {
+ Format::List => {
+ sum += ela_curr.print_list(&options, member.package_id(), sum > 0)?;
+ }
+ Format::Json => {
+ sum += ela_curr.print_json(&options, member.package_id())?;
+ }
}
}
if sum == 0 {
@@ -273,173 +188,15 @@
verbose!(config, "Printing...", "list format");
let mut count = 0;
- if options.flag_format == Some("list".to_string()) {
- count = ela_curr.print_list(&options, root, false)?;
- } else if options.flag_format == Some("json".to_string()) {
- ela_curr.print_json(&options, root)?;
- } else {
- println!("Error, did not specify list or json output formatting");
- std::process::exit(2);
+ match options.format {
+ Format::List => {
+ count = ela_curr.print_list(&options, root, false)?;
+ }
+ Format::Json => {
+ ela_curr.print_json(&options, root)?;
+ }
}
Ok(count)
}
}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- fn options(args: &[&str]) -> Options {
- let mut argv = vec!["cargo", "outdated"];
- if !args.is_empty() {
- argv.extend(args);
- }
- let mut options: Options = Docopt::new(USAGE)
- .and_then(|d| {
- d.version(Some(
- concat!(env!("CARGO_PKG_NAME"), " v", env!("CARGO_PKG_VERSION")).to_owned(),
- ))
- .argv(argv)
- .deserialize()
- })
- .unwrap_or_else(|e| e.exit());
- fn flat_split(arg: &[String]) -> Vec<String> {
- arg.iter()
- .flat_map(|s| s.split_whitespace())
- .flat_map(|s| s.split(','))
- .filter(|s| !s.is_empty())
- .map(ToString::to_string)
- .collect()
- }
- options.flag_features = flat_split(&options.flag_features);
- options.flag_ignore = flat_split(&options.flag_ignore);
- options.flag_exclude = flat_split(&options.flag_exclude);
- options.flag_packages = flat_split(&options.flag_packages);
- if options.flag_root_deps_only {
- options.flag_depth = Some(1);
- }
- options
- }
-
- #[test]
- fn default() {
- let opts = options(&[]);
- assert_eq!(
- Options {
- flag_format: Some("list".into()),
- flag_color: Some("auto".into()),
- ..Options::default()
- },
- opts
- )
- }
-
- #[test]
- fn root_only() {
- let opts = options(&["--root-deps-only"]);
- assert_eq!(
- Options {
- flag_format: Some("list".into()),
- flag_color: Some("auto".into()),
- flag_depth: Some(1),
- flag_root_deps_only: true,
- ..Options::default()
- },
- opts
- )
- }
-
- #[test]
- fn features() {
- let opts1 = options(&["--features=one,two,three"]);
- let opts2 = options(&["--features", "one,two,three"]);
- let opts3 = options(&["--features", "one two three"]);
- // Not supported
- //let opts4 = options("--features one --features two --features three");
- //let opts5 = options("--features one --features two,three");
- let correct = Options {
- flag_format: Some("list".into()),
- flag_color: Some("auto".into()),
- flag_features: vec!["one".into(), "two".into(), "three".into()],
- ..Options::default()
- };
-
- assert_eq!(correct, opts1);
- assert_eq!(correct, opts2);
- assert_eq!(correct, opts3);
- }
-
- #[test]
- fn exclude() {
- let opts1 = options(&["--exclude=one,two,three"]);
- let opts2 = options(&["--exclude", "one,two,three"]);
- let opts3 = options(&["--exclude", "one two three"]);
- // Not supported
- //let opts4 = options("--exclude one two three");
- //let opts5 = options("--exclude one --exclude two --exclude three");
- //let opts6 = options("--exclude one --exclude two,three");
- let correct = Options {
- flag_format: Some("list".into()),
- flag_color: Some("auto".into()),
- flag_exclude: vec!["one".into(), "two".into(), "three".into()],
- ..Options::default()
- };
-
- assert_eq!(correct, opts1);
- assert_eq!(correct, opts2);
- assert_eq!(correct, opts3);
- }
-
- #[test]
- fn ignore() {
- let opts1 = options(&["--ignore=one,two,three"]);
- let opts2 = options(&["--ignore", "one,two,three"]);
- let opts3 = options(&["--ignore", "one two three"]);
- // Not supported
- //let opts4 = options("--ignore one two three");
- //let opts5 = options("--ignore one --ignore two --ignore three");
- //let opts6 = options("--ignore one --ignore two,three");
- let correct = Options {
- flag_format: Some("list".into()),
- flag_color: Some("auto".into()),
- flag_ignore: vec!["one".into(), "two".into(), "three".into()],
- ..Options::default()
- };
-
- assert_eq!(correct, opts1);
- assert_eq!(correct, opts2);
- assert_eq!(correct, opts3);
- }
-
- #[test]
- fn verbose() {
- let opts1 = options(&["--verbose", "--verbose", "--verbose"]);
- let correct = Options {
- flag_format: Some("list".into()),
- flag_color: Some("auto".into()),
- flag_verbose: 3,
- ..Options::default()
- };
-
- assert_eq!(correct, opts1);
- }
-
- #[test]
- fn packages() {
- let opts1 = options(&["--packages", "one,two"]);
- let opts2 = options(&["--packages", "one two"]);
- // Not Supported
- //let opts3 = options(&["--packages","one","--packages","two"]);
- //let opts4 = options(&["--packages", "one", "two"]);
- let correct = Options {
- flag_format: Some("list".into()),
- flag_color: Some("auto".into()),
- flag_packages: vec!["one".into(), "two".into()],
- ..Options::default()
- };
-
- assert_eq!(correct, opts1);
- assert_eq!(correct, opts2);
- }
-}