Merge pull request #635 from matthiasbeyer/bin/clap

Use clap in bin/imag binary.
This commit is contained in:
Matthias Beyer 2016-09-08 13:23:42 +02:00 committed by GitHub
commit 198170cf57
2 changed files with 123 additions and 80 deletions

View file

@ -7,4 +7,12 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
version = "2.0" version = "2.0"
walkdir = "0.1.5" walkdir = "0.1.5"
crossbeam = "0.2.9" crossbeam = "0.2.9"
clap = "2.*"
log = "0.3"
[dependencies.libimagrt]
path = "../libimagrt"
[dependencies.libimagerror]
path = "../libimagerror"

View file

@ -1,7 +1,12 @@
extern crate crossbeam; extern crate crossbeam;
extern crate clap;
#[macro_use] extern crate version; #[macro_use] extern crate version;
#[macro_use] extern crate log;
extern crate walkdir; extern crate walkdir;
extern crate libimagrt;
extern crate libimagerror;
use std::env; use std::env;
use std::process::exit; use std::process::exit;
use std::process::Command; use std::process::Command;
@ -10,11 +15,15 @@ use std::io::ErrorKind;
use walkdir::WalkDir; use walkdir::WalkDir;
use crossbeam::*; use crossbeam::*;
use clap::{Arg, AppSettings, SubCommand};
const DBG_FLAG: &'static str = "--debug"; use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error;
fn help(cmds: Vec<String>) { /// Returns the helptext, putting the Strings in cmds as possible
println!(r#" /// subcommands into it
fn help_text(cmds: Vec<String>) -> String {
let text = format!(r#"
_ _
(_)_ __ ___ __ _ __ _ (_)_ __ ___ __ _ __ _
@ -33,13 +42,8 @@ fn help(cmds: Vec<String>) {
modules can be used independently. modules can be used independently.
Available commands: Available commands:
"#);
for cmd in cmds.iter() { {imagbins}
println!("\t{}", cmd);
}
println!(r#"
Call a command with 'imag <command> <args>' Call a command with 'imag <command> <args>'
Each command can be called with "--help" to get the respective helptext. Each command can be called with "--help" to get the respective helptext.
@ -49,9 +53,16 @@ fn help(cmds: Vec<String>) {
imag is free software. It is released under the terms of LGPLv2.1 imag is free software. It is released under the terms of LGPLv2.1
(c) 2016 Matthias Beyer and contributors"#); (c) 2016 Matthias Beyer and contributors"#, imagbins = cmds.into_iter()
.map(|cmd| format!("\t{}\n", cmd))
.fold(String::new(), |s, c| {
let s = s + c.as_str();
s
}));
text
} }
/// Returns the list of imag-* executables found in $PATH
fn get_commands() -> Vec<String> { fn get_commands() -> Vec<String> {
let path = env::var("PATH"); let path = env::var("PATH");
if path.is_err() { if path.is_err() {
@ -80,7 +91,7 @@ fn get_commands() -> Vec<String> {
.filter_map(|path| { .filter_map(|path| {
path.file_name() path.file_name()
.to_str() .to_str()
.and_then(|s| s.splitn(2, "-").nth(1).map(|s| format!("imag {}", s))) .and_then(|s| s.splitn(2, "-").nth(1).map(String::from))
}) })
.collect() .collect()
}) })
@ -97,59 +108,58 @@ fn get_commands() -> Vec<String> {
execs execs
} }
fn find_command() -> Option<String> {
env::args().skip(1).filter(|x| !x.starts_with("-")).next()
}
fn find_flag() -> Option<String> {
env::args().skip(1).filter(|x| x.starts_with("-")).next()
}
fn is_debug_flag<T: AsRef<str>>(ref s: &T) -> bool {
s.as_ref() == DBG_FLAG
}
fn find_args(command: &str) -> Vec<String> {
env::args()
.skip(1)
.position(|e| e == command)
.map(|pos| env::args().skip(pos + 2).collect::<Vec<String>>())
.unwrap_or(vec![])
}
fn main() { fn main() {
// Initialize the Runtime and build the CLI
let appname = "imag";
let version = &version!();
let about = "imag - the PIM suite for the commandline";
let commands = get_commands(); let commands = get_commands();
let mut args = env::args(); let helptext = help_text(commands.clone());
let _ = args.next(); let app = Runtime::get_default_cli_builder(appname, version, about)
let first_arg = match find_command() { .settings(&[AppSettings::AllowExternalSubcommands, AppSettings::ArgRequiredElseHelp])
Some(s) => s, .arg(Arg::with_name("version")
None => match find_flag() { .long("version")
Some(s) => s, .takes_value(false)
None => { .required(false)
help(commands); .multiple(false)
.help("Get the version of imag"))
.arg(Arg::with_name("versions")
.long("versions")
.takes_value(false)
.required(false)
.multiple(false)
.help("Get the versions of the imag commands"))
.subcommand(SubCommand::with_name("help").help("Show help"))
.help(helptext.as_str());
let rt = Runtime::new(app)
.unwrap_or_else(|e| {
println!("Runtime couldn't be setup. Exiting");
trace_error(&e);
exit(1);
});
let matches = rt.cli();
debug!("matches: {:?}", matches);
// Begin checking for arguments
if matches.is_present("version") {
debug!("Showing version");
println!("imag {}", &version!()[..]);
exit(0); exit(0);
}, }
},
};
let is_debug = env::args().skip(1).find(is_debug_flag).is_some();
match &first_arg[..] { if matches.is_present("versions") {
"--help" | "-h" => { debug!("Showing versions");
help(commands);
exit(0);
},
"--version" => println!("imag {}", &version!()[..]),
"--versions" => {
let mut result = vec![]; let mut result = vec![];
for command in commands.iter() { for command in commands.iter() {
result.push(crossbeam::scope(|scope| { result.push(crossbeam::scope(|scope| {
scope.spawn(|| { scope.spawn(|| {
let v = Command::new(command).arg("--version").output(); let v = Command::new(format!("imag-{}",command)).arg("--version").output();
match v { match v {
Ok(v) => match String::from_utf8(v.stdout) { Ok(v) => match String::from_utf8(v.stdout) {
Ok(s) => format!("{} -> {}", command, s), Ok(s) => format!("{:10} -> {}", command, s),
Err(e) => format!("Failed calling {} -> {:?}", command, e), Err(e) => format!("Failed calling {} -> {:?}", command, e),
}, },
Err(e) => format!("Failed calling {} -> {:?}", command, e), Err(e) => format!("Failed calling {} -> {:?}", command, e),
@ -159,16 +169,33 @@ fn main() {
} }
for versionstring in result.into_iter().map(|handle| handle.join()) { for versionstring in result.into_iter().map(|handle| handle.join()) {
println!("{}", versionstring); // The amount of newlines may differ depending on the subprocess
println!("{}", versionstring.trim());
}
} }
},
s => { // Matches any subcommand given
let mut subcommand_args = find_args(s); match matches.subcommand() {
if is_debug && subcommand_args.iter().find(is_debug_flag).is_none() { (subcommand, Some(scmd)) => {
subcommand_args.insert(0, String::from(DBG_FLAG)); // Get all given arguments and further subcommands to pass to
// the imag-<> binary
// Providing no arguments is OK, and is therefore ignored here
let subcommand_args : Vec<&str> = match scmd.values_of("") {
Some(values) => values.collect(),
None => Vec::new()
};
// Typos happen, so check if the given subcommand is one found in $PATH
if !commands.contains(&String::from(subcommand)) {
println!("No such command: 'imag-{}'", subcommand);
println!("See 'imag --help' for available subcommands");
exit(2);
} }
match Command::new(format!("imag-{}", s))
debug!("Calling 'imag-{}' with args: {:?}", subcommand, subcommand_args);
// Create a Command, and pass it the gathered arguments
match Command::new(format!("imag-{}", subcommand))
.stdin(Stdio::inherit()) .stdin(Stdio::inherit())
.stdout(Stdio::inherit()) .stdout(Stdio::inherit())
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
@ -178,29 +205,37 @@ fn main() {
{ {
Ok(exit_status) => { Ok(exit_status) => {
if !exit_status.success() { if !exit_status.success() {
println!("{} exited with non-zero exit code", s); debug!("{} exited with non-zero exit code: {:?}", subcommand, exit_status);
exit(exit_status.code().unwrap_or(42)); println!("{} exited with non-zero exit code", subcommand);
exit(exit_status.code().unwrap_or(1));
} }
debug!("Successful exit!");
}, },
Err(e) => { Err(e) => {
debug!("Error calling the subcommand");
match e.kind() { match e.kind() {
ErrorKind::NotFound => { ErrorKind::NotFound => {
println!("No such command: 'imag-{}'", s); // With the check above, this absolutely should not happen.
// Keeping it to be safe
println!("No such command: 'imag-{}'", subcommand);
println!("See 'imag --help' for available subcommands");
exit(2); exit(2);
}, },
ErrorKind::PermissionDenied => { ErrorKind::PermissionDenied => {
println!("No permission to execute: 'imag-{}'", s); println!("No permission to execute: 'imag-{}'", subcommand);
exit(1); exit(1);
}, },
_ => { _ => {
println!("Error spawning: {:?}", e); println!("Error spawning: {:?}", e);
exit(1337); exit(1);
} }
} }
} }
} }
}, },
// Calling for example 'imag --versions' will lead here, as this option does not exit.
// There's nothing to do in such a case
_ => {},
} }
} }