use Command;
use Message;
use CmdWrapper;
use CmdResult;
use std::env;
use std::io::Write;
use getopts::Options;
use getopts::ParsingStyle;
use tabwriter::TabWriter;
use strsim::damerau_levenshtein;
pub struct CmdHandler<'a> {
description: Option<&'a str>,
commands: Vec<Box<Command>>,
program_name: String,
args: Vec<String>,
}
impl<'a> CmdHandler<'a> {
pub fn new() -> CmdHandler<'a> {
let args: Vec<String> = env::args().collect();
let program_name = args[0].clone();
CmdHandler {
description: None,
commands: Vec::new(),
program_name: program_name,
args: args,
}
}
pub fn set_description(&mut self, descr: &'a str) {
self.description = Some(descr);
}
pub fn get_description(&mut self) -> &'a str {
match self.description {
Some(descr) => descr,
None => "",
}
}
pub fn override_args(&mut self, args: Vec<String>) {
self.args = args;
}
pub fn add(&mut self, command: Box<Command>) {
self.commands.push(command);
}
fn short_usage(&self) -> String {
let mut usage = String::with_capacity(150);
usage.push_str("Usage:\n");
usage.push_str(&format!("\t{} <command> [<args>...]\n", self.program_name));
usage.push_str(&format!("\t{} [options]", self.program_name));
usage
}
fn help(&self, opts: &Options) -> CmdResult {
let mut brief = String::with_capacity(250);
let mut msg = Message::new();
match self.description {
Some(descr) => brief.push_str(&format!("{}\n\n", descr)),
None => {}
}
brief.push_str(&self.short_usage());
msg.add_line(&opts.usage(&brief));
msg.add_line("Commands are:");
let mut tw = TabWriter::new(Vec::new());
for cmd in self.commands.iter() {
write!(&mut tw, " {}\t{}\n", cmd.name(), cmd.description()).unwrap();
}
tw.flush().unwrap();
msg.add_line(&String::from_utf8(tw.unwrap()).unwrap());
msg.add_line(&format!("\nSee '{} help <command>' for more information ",
self.program_name));
msg.add_line("on a specific command.");
CmdResult::Help(msg)
}
fn bad_usage(&self) -> CmdResult {
let mut msg = Message::new();
msg.set_error(true);
msg.add_line("Invalid arguments.");
msg.add_line(&self.short_usage());
CmdResult::BadUsage(msg)
}
pub fn run(mut self) -> CmdResult {
let mut opts = Options::new();
opts.parsing_style(ParsingStyle::StopAtFirstFree);
opts.optflag("h", "help", "print this help menu");
let matches = match opts.parse(&self.args[1..]) {
Ok(m) => m,
Err(_) => {
return self.bad_usage();
}
};
if matches.opt_present("h") {
if matches.free.len() != 0 {
return self.bad_usage();
}
return self.help(&opts);
}
let command = if !matches.free.is_empty() {
matches.free[0].clone()
} else {
return self.bad_usage();
};
for index in 0..self.commands.len() {
if self.commands[index].name() == command {
let wrap = CmdWrapper::new(self.commands.remove(index), self.args);
return CmdResult::Cmd(wrap);
}
}
if (command == "help") && (matches.free.len() == 2) {
return self.help_for_command(&matches.free[1]);
}
let mut sim_cmd: Option<&Box<Command>> = None;
let mut lowest_sim: usize = 3;
for cmd in self.commands.iter() {
let new_sim = damerau_levenshtein(cmd.name(), &command);
if new_sim < lowest_sim {
lowest_sim = new_sim;
sim_cmd = Some(cmd);
}
}
match sim_cmd {
Some(cmd) => {
let mut msg = Message::new();
msg.set_error(true);
msg.add_line("No such subcommand\n");
msg.add_line(&format!(" Did you mean `{}`?", cmd.name()));
return CmdResult::BadUsage(msg);
}
None => {}
};
let mut msg = Message::new();
msg.set_error(true);
msg.add_line("No such subcommand");
CmdResult::UnknowCmd(msg)
}
fn help_for_command(&mut self, name: &str) -> CmdResult {
for index in 0..self.commands.len() {
if self.commands[index].name() == name {
let wrap = CmdWrapper::new(self.commands.remove(index), self.args.clone());
return CmdResult::HelpForCmd(wrap);
};
}
self.bad_usage()
}
}
#[cfg(test)]
mod tests {
use super::*;
use Command;
use CmdResult;
struct CmdA;
impl Command for CmdA {
fn name<'a>(&self) -> &'a str {
"cmd-a"
}
fn help<'a>(&self) -> &'a str {
"HELP"
}
fn description<'a>(&self) -> &'a str {
"DESCR"
}
fn run(&self, argv: &Vec<String>) {
}
}
struct AnotherCmd;
impl Command for AnotherCmd {
fn name<'a>(&self) -> &'a str {
"another-cmd"
}
fn help<'a>(&self) -> &'a str {
"HELP another"
}
fn description<'a>(&self) -> &'a str {
"DESCR another"
}
fn run(&self, argv: &Vec<String>) {
}
}
#[test]
fn test_usage() {
let mut handler = CmdHandler::new();
let args: Vec<String> = vec!["bin".to_string(), "-h".to_string()];
handler.override_args(args);
match handler.run() {
CmdResult::Help(msg) => {
assert!(msg.get().contains("Usage"));
assert!(msg.get().contains("Commands are:"));
}
_ => unreachable!(),
}
}
#[test]
fn test_bad_usage() {
let mut handler = CmdHandler::new();
let args: Vec<String> = vec!["bin".to_string(), "--unknow".to_string()];
handler.override_args(args);
match handler.run() {
CmdResult::BadUsage(msg) => {
assert!(msg.get().contains("Invalid argument"));
}
_ => unreachable!(),
}
}
#[test]
fn test_bad_command() {
let mut handler = CmdHandler::new();
let args: Vec<String> = vec!["bin".to_string(), "cmd-b".to_string()];
handler.override_args(args);
handler.add(Box::new(CmdA));
handler.add(Box::new(AnotherCmd));
match handler.run() {
CmdResult::BadUsage(msg) => {
assert!(msg.get().contains("cmd-a"));
}
_ => unreachable!(),
}
}
#[test]
fn test_unknow_cmd() {
let mut handler = CmdHandler::new();
let args: Vec<String> = vec!["bin".to_string(), "bbbbbbbbbbb".to_string()];
handler.override_args(args);
handler.add(Box::new(CmdA));
handler.add(Box::new(CmdA));
match handler.run() {
CmdResult::UnknowCmd(msg) => assert!(msg.get().contains("No such subcommand")),
_ => unreachable!(),
}
}
#[test]
fn test_cmd() {
let mut handler = CmdHandler::new();
let args: Vec<String> = vec!["bin".to_string(), "cmd-a".to_string()];
handler.override_args(args);
handler.add(Box::new(CmdA));
handler.add(Box::new(CmdA));
match handler.run() {
CmdResult::Cmd(cmd) => assert_eq!(cmd.name(), "cmd-a"),
_ => unreachable!(),
}
}
#[test]
fn test_help_for_cmd() {
let mut handler = CmdHandler::new();
let args: Vec<String> = vec!["bin".to_string(), "help".to_string(), "cmd-a".to_string()];
handler.override_args(args);
handler.add(Box::new(CmdA));
handler.add(Box::new(CmdA));
match handler.run() {
CmdResult::HelpForCmd(cmd) => assert_eq!(cmd.name(), "cmd-a"),
_ => unreachable!(),
}
}
}