Remove calls to exit() and replace them with error propagation up to main()

Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
This commit is contained in:
Matthias Beyer 2019-10-19 08:54:57 +02:00
parent 19f6391d8a
commit e259015a61
2 changed files with 143 additions and 195 deletions

View file

@ -20,7 +20,6 @@ libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror"
libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }
libimagentrytag = { version = "0.10.0", path = "../../../lib/entry/libimagentrytag" } libimagentrytag = { version = "0.10.0", path = "../../../lib/entry/libimagentrytag" }
libimagutil = { version = "0.10.0", path = "../../../lib/etc/libimagutil" } libimagutil = { version = "0.10.0", path = "../../../lib/etc/libimagutil" }
failure = "0.1.5"
log = "0.4.6" log = "0.4.6"
# Build time dependencies for cli completion # Build time dependencies for cli completion
@ -61,6 +60,7 @@ walkdir = "2.2.8"
log = "0.4.6" log = "0.4.6"
toml = "0.5.1" toml = "0.5.1"
toml-query = "0.9.2" toml-query = "0.9.2"
failure = "0.1.5"
libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" } libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" }
libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }

View file

@ -36,6 +36,7 @@
extern crate clap; extern crate clap;
#[macro_use] extern crate log; #[macro_use] extern crate log;
#[macro_use] extern crate failure;
extern crate walkdir; extern crate walkdir;
extern crate toml; extern crate toml;
extern crate toml_query; extern crate toml_query;
@ -44,11 +45,10 @@ extern crate toml_query;
extern crate libimagerror; extern crate libimagerror;
use std::env; use std::env;
use std::process::exit;
use std::process::Command; use std::process::Command;
use std::process::Stdio; use std::process::Stdio;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::io::{stdout, Stdout, Write}; use std::io::{stdout, Write};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::path::PathBuf; use std::path::PathBuf;
@ -56,12 +56,13 @@ use walkdir::WalkDir;
use clap::{Arg, ArgMatches, AppSettings, SubCommand}; use clap::{Arg, ArgMatches, AppSettings, SubCommand};
use toml::Value; use toml::Value;
use toml_query::read::TomlValueReadExt; use toml_query::read::TomlValueReadExt;
use failure::Error;
use failure::ResultExt;
use failure::err_msg;
use failure::Fallible as Result;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagrt::spec::CliSpec; use libimagrt::spec::CliSpec;
use libimagerror::io::ToExitCode;
use libimagerror::exit::ExitUnwrap;
use libimagerror::trace::trace_error;
use libimagrt::configuration::InternalConfiguration; use libimagrt::configuration::InternalConfiguration;
/// Returns the helptext, putting the Strings in cmds as possible /// Returns the helptext, putting the Strings in cmds as possible
@ -107,54 +108,44 @@ fn help_text(cmds: Vec<String>) -> String {
} }
/// Returns the list of imag-* executables found in $PATH /// Returns the list of imag-* executables found in $PATH
fn get_commands(out: &mut Stdout) -> Vec<String> { fn get_commands() -> Result<Vec<String>> {
let mut v = match env::var("PATH") { let mut v = env::var("PATH")?
Err(e) => { .split(':')
writeln!(out, "PATH error: {:?}", e) .flat_map(|elem| {
.to_exit_code() WalkDir::new(elem)
.unwrap_or_exit(); .max_depth(1)
exit(1) .into_iter()
}, .filter(|path| match *path {
Ok(ref p) => p.file_name().to_str().map_or(false, |f| f.starts_with("imag-")),
Ok(path) => path Err(_) => false,
.split(':') })
.flat_map(|elem| { .filter_map(|r| r.ok())
WalkDir::new(elem) .filter_map(|path| path
.max_depth(1) .file_name()
.into_iter() .to_str()
.filter(|path| match *path { .and_then(|s| s.splitn(2, '-').nth(1).map(String::from))
Ok(ref p) => p.file_name().to_str().map_or(false, |f| f.starts_with("imag-")), )
Err(_) => false, })
}) .filter(|path| if cfg!(debug_assertions) {
.filter_map(Result::ok) // if we compile in debug mode during development, ignore everything that ends with
.filter_map(|path| path // ".d", as developers might use the ./target/debug/ directory directly in `$PATH`.
.file_name() !path.ends_with(".d")
.to_str() } else {
.and_then(|s| s.splitn(2, '-').nth(1).map(String::from)) true
) })
}) .collect::<Vec<String>>();
.filter(|path| if cfg!(debug_assertions) {
// if we compile in debug mode during development, ignore everything that ends with
// ".d", as developers might use the ./target/debug/ directory directly in `$PATH`.
!path.ends_with(".d")
} else {
true
})
.collect::<Vec<String>>()
};
v.sort(); v.sort();
v Ok(v)
} }
fn main() { fn main() -> Result<()> {
// Initialize the Runtime and build the CLI // Initialize the Runtime and build the CLI
let appname = "imag"; let appname = "imag";
let version = make_imag_version!(); let version = make_imag_version!();
let about = "imag - the PIM suite for the commandline"; let about = "imag - the PIM suite for the commandline";
let mut out = stdout(); let commands = get_commands()?;
let commands = get_commands(&mut out);
let helptext = help_text(commands.clone()); let helptext = help_text(commands.clone());
let mut app = Runtime::get_default_cli_builder(appname, &version, about) let mut app = Runtime::get_default_cli_builder(appname, &version, about)
.settings(&[AppSettings::AllowExternalSubcommands, AppSettings::ArgRequiredElseHelp]) .settings(&[AppSettings::AllowExternalSubcommands, AppSettings::ArgRequiredElseHelp])
@ -175,174 +166,131 @@ fn main() {
let long_help = { let long_help = {
let mut v = vec![]; let mut v = vec![];
if let Err(e) = app.write_long_help(&mut v) { app.write_long_help(&mut v)?;
eprintln!("Error: {:?}", e); String::from_utf8(v).map_err(|_| err_msg("UTF8 Error"))?
exit(1);
}
String::from_utf8(v).unwrap_or_else(|_| { eprintln!("UTF8 Error"); exit(1) })
}; };
{ let print_help = app.clone().get_matches().subcommand_name().map(|h| h == "help").unwrap_or(false);
let print_help = app.clone().get_matches().subcommand_name().map(|h| h == "help").unwrap_or(false);
if print_help { let mut out = stdout();
writeln!(out, "{}", long_help) if print_help {
.to_exit_code() writeln!(out, "{}", long_help).map_err(Error::from)
.unwrap_or_exit(); } else {
exit(0) let enable_logging = app.enable_logging();
let matches = app.matches();
let rtp = ::libimagrt::runtime::get_rtp_match(&matches)?;
let configpath = matches
.value_of("config")
.map_or_else(|| rtp.clone(), PathBuf::from);
debug!("Config path = {:?}", configpath);
let config = ::libimagrt::configuration::fetch_config(&configpath)?;
if enable_logging {
Runtime::init_logger(&matches, config.as_ref())
} }
}
let enable_logging = app.enable_logging(); debug!("matches: {:?}", matches);
let matches = app.matches();
let rtp = ::libimagrt::runtime::get_rtp_match(&matches) // Begin checking for arguments
.unwrap_or_else(|e| {
trace_error(&e);
exit(1)
});
let configpath = matches
.value_of("config")
.map_or_else(|| rtp.clone(), PathBuf::from);
debug!("Config path = {:?}", configpath);
let config = ::libimagrt::configuration::fetch_config(&configpath)
.unwrap_or_else(|e| {
trace_error(&e);
exit(1)
});
if enable_logging { if matches.is_present("version") {
Runtime::init_logger(&matches, config.as_ref()) debug!("Showing version");
} writeln!(out, "imag {}", env!("CARGO_PKG_VERSION")).map_err(Error::from)
} else {
if matches.is_present("versions") {
debug!("Showing versions");
commands
.iter()
.map(|command| {
match Command::new(format!("imag-{}", command))
.stdin(::std::process::Stdio::inherit())
.stdout(::std::process::Stdio::piped())
.stderr(::std::process::Stdio::inherit())
.arg("--version")
.output()
.map(|v| v.stdout)
{
Ok(s) => match String::from_utf8(s) {
Ok(s) => format!("{:15} -> {}", command, s),
Err(e) => format!("UTF8 Error while working with output of imag{}: {:?}", command, e),
},
Err(e) => format!("Failed calling imag-{} -> {:?}", command, e),
}
})
.fold(Ok(()), |_, line| {
// The amount of newlines may differ depending on the subprocess
writeln!(out, "{}", line.trim()).map_err(Error::from)
})
} else {
let aliases = fetch_aliases(config.as_ref())
.map_err(Error::from)
.context("Error while fetching aliases from configuration file")?;
debug!("matches: {:?}", matches); // Matches any subcommand given, except calling for example 'imag --versions', as this option
// does not exit. There's nothing to do in such a case
if let (subcommand, Some(scmd)) = matches.subcommand() {
// Get all given arguments and further subcommands to pass to
// the imag-<> binary
// Providing no arguments is OK, and is therefore ignored here
let mut subcommand_args : Vec<String> = match scmd.values_of("") {
Some(values) => values.map(String::from).collect(),
None => Vec::new()
};
// Begin checking for arguments debug!("Processing forwarding of commandline arguments");
forward_commandline_arguments(&matches, &mut subcommand_args);
if matches.is_present("version") { let subcommand = String::from(subcommand);
debug!("Showing version"); let subcommand = aliases.get(&subcommand).cloned().unwrap_or(subcommand);
writeln!(out, "imag {}", env!("CARGO_PKG_VERSION"))
.to_exit_code()
.unwrap_or_exit();
exit(0);
}
if matches.is_present("versions") { debug!("Calling 'imag-{}' with args: {:?}", subcommand, subcommand_args);
debug!("Showing versions");
commands
.iter()
.map(|command| {
match Command::new(format!("imag-{}", command))
.stdin(::std::process::Stdio::inherit())
.stdout(::std::process::Stdio::piped())
.stderr(::std::process::Stdio::inherit())
.arg("--version")
.output()
.map(|v| v.stdout)
{
Ok(s) => match String::from_utf8(s) {
Ok(s) => format!("{:15} -> {}", command, s),
Err(e) => format!("UTF8 Error while working with output of imag{}: {:?}", command, e),
},
Err(e) => format!("Failed calling imag-{} -> {:?}", command, e),
}
})
.fold((), |_, line| {
// The amount of newlines may differ depending on the subprocess
writeln!(out, "{}", line.trim())
.to_exit_code()
.unwrap_or_exit();
});
exit(0); // Create a Command, and pass it the gathered arguments
} match Command::new(format!("imag-{}", subcommand))
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.args(&subcommand_args[..])
.spawn()
.and_then(|mut c| c.wait())
{
Ok(exit_status) => if !exit_status.success() {
debug!("imag-{} exited with non-zero exit code: {:?}", subcommand, exit_status);
Err(format_err!("imag-{} exited with non-zero exit code", subcommand))
} else {
debug!("Successful exit!");
Ok(())
},
let aliases = match fetch_aliases(config.as_ref()) { Err(e) => {
Ok(aliases) => aliases, debug!("Error calling the subcommand");
Err(e) => { match e.kind() {
writeln!(out, "Error while fetching aliases from configuration file") ErrorKind::NotFound => {
.to_exit_code() writeln!(out, "No such command: 'imag-{}'", subcommand)?;
.unwrap_or_exit(); writeln!(out, "See 'imag --help' for available subcommands").map_err(Error::from)
debug!("Error = {:?}", e); },
writeln!(out, "Aborting") ErrorKind::PermissionDenied => {
.to_exit_code() writeln!(out, "No permission to execute: 'imag-{}'", subcommand).map_err(Error::from)
.unwrap_or_exit(); },
exit(1); _ => writeln!(out, "Error spawning: {:?}", e).map_err(Error::from),
} }
}; }
// Matches any subcommand given, except calling for example 'imag --versions', as this option
// does not exit. There's nothing to do in such a case
if let (subcommand, Some(scmd)) = matches.subcommand() {
// Get all given arguments and further subcommands to pass to
// the imag-<> binary
// Providing no arguments is OK, and is therefore ignored here
let mut subcommand_args : Vec<String> = match scmd.values_of("") {
Some(values) => values.map(String::from).collect(),
None => Vec::new()
};
debug!("Processing forwarding of commandline arguments");
forward_commandline_arguments(&matches, &mut subcommand_args);
let subcommand = String::from(subcommand);
let subcommand = aliases.get(&subcommand).cloned().unwrap_or(subcommand);
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())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.args(&subcommand_args[..])
.spawn()
.and_then(|mut c| c.wait())
{
Ok(exit_status) => {
if !exit_status.success() {
debug!("imag-{} exited with non-zero exit code: {:?}", subcommand, exit_status);
eprintln!("imag-{} exited with non-zero exit code", subcommand);
exit(exit_status.code().unwrap_or(1));
}
debug!("Successful exit!");
},
Err(e) => {
debug!("Error calling the subcommand");
match e.kind() {
ErrorKind::NotFound => {
writeln!(out, "No such command: 'imag-{}'", subcommand)
.to_exit_code()
.unwrap_or_exit();
writeln!(out, "See 'imag --help' for available subcommands")
.to_exit_code()
.unwrap_or_exit();
exit(1);
},
ErrorKind::PermissionDenied => {
writeln!(out, "No permission to execute: 'imag-{}'", subcommand)
.to_exit_code()
.unwrap_or_exit();
exit(1);
},
_ => {
writeln!(out, "Error spawning: {:?}", e)
.to_exit_code()
.unwrap_or_exit();
exit(1);
} }
} else {
Ok(())
} }
} }
} }
} }
} }
fn fetch_aliases(config: Option<&Value>) -> Result<BTreeMap<String, String>, String> { fn fetch_aliases(config: Option<&Value>) -> Result<BTreeMap<String, String>> {
let cfg = config.ok_or_else(|| String::from("No configuration found"))?; let cfg = config.ok_or_else(|| err_msg("No configuration found"))?;
let value = cfg let value = cfg
.read("imag.aliases") .read("imag.aliases")
.map_err(|_| String::from("Reading from config failed")); .map_err(|_| err_msg("Reading from config failed"))?;
match value? { match value {
None => Ok(BTreeMap::new()), None => Ok(BTreeMap::new()),
Some(&Value::Table(ref tbl)) => { Some(&Value::Table(ref tbl)) => {
let mut alias_mappings = BTreeMap::new(); let mut alias_mappings = BTreeMap::new();
@ -359,7 +307,7 @@ fn fetch_aliases(config: Option<&Value>) -> Result<BTreeMap<String, String>, Str
alias_mappings.insert(s.clone(), k.clone()); alias_mappings.insert(s.clone(), k.clone());
}, },
_ => { _ => {
let e = format!("Not all values are a String in 'imag.aliases.{}'", k); let e = format_err!("Not all values are a String in 'imag.aliases.{}'", k);
return Err(e); return Err(e);
} }
} }
@ -367,7 +315,7 @@ fn fetch_aliases(config: Option<&Value>) -> Result<BTreeMap<String, String>, Str
}, },
_ => { _ => {
let msg = format!("Type Error: 'imag.aliases.{}' is not a table or string", k); let msg = format_err!("Type Error: 'imag.aliases.{}' is not a table or string", k);
return Err(msg); return Err(msg);
}, },
} }
@ -376,7 +324,7 @@ fn fetch_aliases(config: Option<&Value>) -> Result<BTreeMap<String, String>, Str
Ok(alias_mappings) Ok(alias_mappings)
}, },
Some(_) => Err(String::from("Type Error: 'imag.aliases' is not a table")), Some(_) => Err(err_msg("Type Error: 'imag.aliases' is not a table")),
} }
} }