clidispatch: support command name aliases

Summary:
Support command name aliases like `config|debugconfig` in Python command
definition.

Reviewed By: sfilipco

Differential Revision: D18108170

fbshipit-source-id: eeb9ec273960d7bd4e4278e55ef8122f200b2f60
This commit is contained in:
Jun Wu 2019-11-05 21:34:09 -08:00 committed by Facebook Github Bot
parent 7283f8a4da
commit 8f86961b12
3 changed files with 32 additions and 6 deletions

View File

@ -8,8 +8,9 @@
use crate::{io::IO, repo::Repo};
use cliparser::parser::{Flag, ParseOutput, StructFlags};
use failure::Fallible;
use std::collections::BTreeMap;
use std::convert::{TryFrom, TryInto};
use std::{collections::BTreeMap, ops::Deref};
use std::ops::Deref;
pub enum CommandFunc {
NoRepo(Box<dyn Fn(ParseOutput, &mut IO) -> Fallible<u8>>),
@ -56,15 +57,36 @@ impl CommandDefinition {
}
}
#[derive(Default)]
pub struct CommandTable {
commands: BTreeMap<String, CommandDefinition>,
/// Alias name -> Command name.
alias: BTreeMap<String, String>,
}
impl CommandTable {
pub fn new() -> Self {
CommandTable {
commands: BTreeMap::new(),
Default::default()
}
/// Insert aliases to `alias` field.
///
/// For example, `insert_aliases("config|cfg")` will insert
/// `{"config": "config|cfg", "cfg": "config|cfg"}` to `alias`.
fn insert_aliases<'a>(&mut self, names: &'a str) {
if !names.contains("|") {
return;
}
for name in names.split("|") {
self.alias.insert(name.to_string(), names.to_string());
}
}
/// Look up a command by name. Consider aliases.
pub fn get(&self, name: &str) -> Option<&CommandDefinition> {
let name = self.alias.get(name).map(AsRef::as_ref).unwrap_or(name);
self.commands.get(name)
}
}
@ -87,6 +109,7 @@ where
FN: Fn(S, &mut IO) -> Fallible<u8> + 'static,
{
fn register(&mut self, f: FN, name: &str, doc: &str) {
self.insert_aliases(name);
let func = move |opts: ParseOutput, io: &mut IO| f(opts.try_into()?, io);
let func = CommandFunc::NoRepo(Box::new(func));
let def = CommandDefinition::new(name, doc, S::flags, func);
@ -101,6 +124,7 @@ where
FN: Fn(S, &mut IO, Option<Repo>) -> Fallible<u8> + 'static,
{
fn register(&mut self, f: FN, name: &str, doc: &str) {
self.insert_aliases(name);
let func =
move |opts: ParseOutput, io: &mut IO, repo: Option<Repo>| f(opts.try_into()?, io, repo);
let func = CommandFunc::OptionalRepo(Box::new(func));
@ -116,6 +140,7 @@ where
FN: Fn(S, &mut IO, Repo) -> Fallible<u8> + 'static,
{
fn register(&mut self, f: FN, name: &str, doc: &str) {
self.insert_aliases(name);
let func = move |opts: ParseOutput, io: &mut IO, repo: Repo| f(opts.try_into()?, io, repo);
let func = CommandFunc::Repo(Box::new(func));
let def = CommandDefinition::new(name, doc, S::flags, func);

View File

@ -234,16 +234,17 @@ pub fn dispatch(command_table: &CommandTable, args: Vec<String>, io: &mut IO) ->
let command_name = first_arg.to_string();
let (expanded, _first_arg_index) = expand_aliases(alias_lookup, &args[first_arg_index..])?;
let (command_name, command_arg_len) =
find_command_name(|name| command_table.contains_key(name), &expanded)
find_command_name(|name| command_table.get(name).is_some(), &expanded)
.ok_or_else(|| errors::UnknownCommand(command_name))?;
let mut new_args = Vec::with_capacity(args.len());
new_args.extend_from_slice(&args[..first_arg_index]);
new_args.push(command_name.clone());
new_args.extend_from_slice(&expanded[command_arg_len..]);
let full_args = new_args;
let def = &command_table[&command_name];
let def = command_table.get(&command_name).unwrap();
let parsed = parse(&def, &full_args)?;
let global_opts: HgGlobalOpts = parsed.clone().try_into()?;

View File

@ -39,7 +39,7 @@ pub fn table() -> CommandTable {
"debugstore",
"print information about blobstore",
);
table.register(debugpython, "debugpython", "run python interpreter");
table.register(debugpython, "debugpython|debugpy", "run python interpreter");
table.register(debugargs, "debug-args", "print arguments received");
table.register(
debugindexedlogdump,