diff --git a/src/bin/cargo/main.rs b/src/bin/cargo/main.rs index 9ed135cf123..e38ee671d69 100644 --- a/src/bin/cargo/main.rs +++ b/src/bin/cargo/main.rs @@ -113,6 +113,14 @@ fn list_commands(config: &Config) -> BTreeSet { commands } +/// List all runnable aliases +fn list_aliases(config: &Config) -> Vec { + match config.get_subkeys("alias") { + Ok(aliases) => aliases, + Err(_) => Vec::new(), + } +} + fn execute_external_subcommand(config: &Config, cmd: &str, args: &[&str]) -> CliResult { let command_exe = format!("cargo-{}{}", cmd, env::consts::EXE_SUFFIX); let path = search_directories(config) @@ -122,8 +130,13 @@ fn execute_external_subcommand(config: &Config, cmd: &str, args: &[&str]) -> Cli let command = match path { Some(command) => command, None => { - let cmds = list_commands(config); - let did_you_mean = closest_msg(cmd, cmds.iter(), |c| c.name()); + let commands: Vec = list_commands(config) + .iter() + .map(|c| c.name().to_string()) + .collect(); + let aliases = list_aliases(config); + let suggestions = commands.iter().chain(aliases.iter()); + let did_you_mean = closest_msg(cmd, suggestions, |c| c); let err = failure::format_err!("no such subcommand: `{}`{}", cmd, did_you_mean); return Err(CliError::new(err, 101)); } diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index af49d052a3c..72c18a8bbe3 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -369,6 +369,57 @@ impl Config { Ok(Some(val.clone())) } + // Gets the subkeys defined for a given key. + pub fn get_subkeys(&self, key: &str) -> CargoResult> { + let vals = self.values()?; + let mut parts = key.split('.').enumerate(); + let mut val = match vals.get(parts.next().unwrap().1) { + Some(val) => val, + None => return Ok(Vec::new()), + }; + + for (i, part) in parts { + match *val { + CV::Table(ref map, _) => { + val = match map.get(part) { + Some(val) => val, + None => return Ok(Vec::new()), + } + } + CV::Integer(_, ref path) + | CV::String(_, ref path) + | CV::List(_, ref path) + | CV::Boolean(_, ref path) => { + let idx = key.split('.').take(i).fold(0, |n, s| n + s.len()) + i - 1; + let key_so_far = &key[..idx]; + failure::bail!( + "expected table for configuration key `{}`, \ + but found {} in {}", + key_so_far, + val.desc(), + path.display() + ) + } + } + } + + let table = match *val { + CV::Table(ref map, _) => map, + CV::Integer(_, ref path) + | CV::String(_, ref path) + | CV::List(_, ref path) + | CV::Boolean(_, ref path) => failure::bail!( + "expected table for configuration key `{}`, \ + but found {} in {}", + key, + val.desc(), + path.display() + ), + }; + + Ok(table.keys().map(|key| key.clone()).collect()) + } + // Helper primarily for testing. pub fn set_env(&mut self, env: HashMap) { self.env = env; diff --git a/tests/testsuite/cargo_command.rs b/tests/testsuite/cargo_command.rs index e0f4cda2e6b..a09751acc77 100644 --- a/tests/testsuite/cargo_command.rs +++ b/tests/testsuite/cargo_command.rs @@ -168,6 +168,49 @@ error: no such subcommand: `biuld` .run(); } +#[cargo_test] +fn find_closest_alias() { + let root = paths::root(); + let my_home = root.join("my_home"); + fs::create_dir(&my_home).unwrap(); + File::create(&my_home.join("config")) + .unwrap() + .write_all( + br#" + [alias] + myalias = "build" + "#, + ) + .unwrap(); + + cargo_process("myalais") + .env("CARGO_HOME", &my_home) + .with_status(101) + .with_stderr_contains( + "\ +error: no such subcommand: `myalais` + +Did you mean `myalias`? +", + ) + .run(); + + // But, if no alias is defined, it must not suggest one! + cargo_process("myalais") + .with_status(101) + .with_stderr_contains( + "\ +error: no such subcommand: `myalais` +", + ) + .with_stderr_does_not_contain( + "\ +Did you mean `myalias`? +", + ) + .run(); +} + // If a subcommand is more than an edit distance of 3 away, we don't make a suggestion. #[cargo_test] fn find_closest_dont_correct_nonsense() {