diff --git a/bin/core/imag-annotate/src/main.rs b/bin/core/imag-annotate/src/main.rs index 790ce8fb..d196c7cb 100644 --- a/bin/core/imag-annotate/src/main.rs +++ b/bin/core/imag-annotate/src/main.rs @@ -57,7 +57,6 @@ use libimagrt::runtime::Runtime; use libimagrt::setup::generate_runtime_setup; use libimagstore::store::FileLockEntry; use libimagstore::storeid::IntoStoreId; -use libimagutil::warn_exit::warn_exit; mod ui; @@ -75,7 +74,13 @@ fn main() { "add" => add(&rt), "remove" => remove(&rt), "list" => list(&rt), - _ => warn_exit("No commandline call", 1) + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-annotation", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(std::process::exit); + }, } }); } diff --git a/bin/core/imag-gps/src/main.rs b/bin/core/imag-gps/src/main.rs index 3277547b..502497a2 100644 --- a/bin/core/imag-gps/src/main.rs +++ b/bin/core/imag-gps/src/main.rs @@ -53,7 +53,6 @@ use libimagentrygps::types::*; use libimagentrygps::entry::*; use libimagrt::setup::generate_runtime_setup; use libimagrt::runtime::Runtime; -use libimagutil::warn_exit::warn_exit; use libimagerror::trace::MapErrTrace; use libimagerror::exit::ExitUnwrap; use libimagerror::io::ToExitCode; @@ -75,7 +74,13 @@ fn main() { "add" => add(&rt), "remove" => remove(&rt), "get" => get(&rt), - _ => warn_exit("No commandline call", 1) + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-gps", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(::std::process::exit); + } } }); } diff --git a/bin/core/imag-link/src/main.rs b/bin/core/imag-link/src/main.rs index bc6af042..4dd8e0de 100644 --- a/bin/core/imag-link/src/main.rs +++ b/bin/core/imag-link/src/main.rs @@ -104,7 +104,13 @@ fn main() { "remove" => remove_linking(&rt), "unlink" => unlink(&rt), "list" => list_linkings(&rt), - _ => panic!("BUG"), + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-link", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(::std::process::exit); + }, } }) .or_else(|| { diff --git a/bin/core/imag-ref/src/main.rs b/bin/core/imag-ref/src/main.rs index d806ff95..96dedad9 100644 --- a/bin/core/imag-ref/src/main.rs +++ b/bin/core/imag-ref/src/main.rs @@ -67,8 +67,12 @@ fn main() { match name { "deref" => deref(&rt), "remove" => remove(&rt), - _ => { - debug!("Unknown command"); // More error handling + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-ref", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(::std::process::exit); }, }; }); diff --git a/bin/core/imag-store/src/main.rs b/bin/core/imag-store/src/main.rs index 60f80d1c..5ec16ce6 100644 --- a/bin/core/imag-store/src/main.rs +++ b/bin/core/imag-store/src/main.rs @@ -50,6 +50,7 @@ extern crate libimagutil; extern crate libimagutil; use libimagrt::setup::generate_runtime_setup; +use libimagerror::trace::MapErrTrace; mod create; mod delete; @@ -92,9 +93,12 @@ fn main() { "update" => update(&rt), "verify" => verify(&rt), "dump" => dump(&mut rt), - _ => { + other => { debug!("Unknown command"); - // More error handling + let _ = rt.handle_unknown_subcommand("imag-store", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(std::process::exit); }, }; } else { diff --git a/bin/core/imag-tag/src/main.rs b/bin/core/imag-tag/src/main.rs index 39f468a5..200456cd 100644 --- a/bin/core/imag-tag/src/main.rs +++ b/bin/core/imag-tag/src/main.rs @@ -86,9 +86,12 @@ fn main() { debug!("id = {:?}, add = {:?}, rem = {:?}", id, add, rem); alter(&rt, id, add, rem); }, - _ => { - error!("Unknown command"); - ::std::process::exit(1) + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-tag", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(std::process::exit); }, }); } diff --git a/bin/domain/imag-bookmark/src/main.rs b/bin/domain/imag-bookmark/src/main.rs index 48f349c4..3438ee78 100644 --- a/bin/domain/imag-bookmark/src/main.rs +++ b/bin/domain/imag-bookmark/src/main.rs @@ -77,8 +77,12 @@ fn main() { "collection" => collection(&rt), "list" => list(&rt), "remove" => remove(&rt), - _ => { - debug!("Unknown command"); // More error handling + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-bookmark", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(::std::process::exit); }, } }); diff --git a/bin/domain/imag-contact/src/main.rs b/bin/domain/imag-contact/src/main.rs index 3c898d60..e8dda553 100644 --- a/bin/domain/imag-contact/src/main.rs +++ b/bin/domain/imag-contact/src/main.rs @@ -102,8 +102,12 @@ fn main() { "show" => show(&rt), "find" => find(&rt), "create" => create(&rt), - _ => { - error!("Unknown command"); // More error handling + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-contact", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(::std::process::exit); }, } }); diff --git a/bin/domain/imag-diary/src/main.rs b/bin/domain/imag-diary/src/main.rs index ccaf3ab8..201f8431 100644 --- a/bin/domain/imag-diary/src/main.rs +++ b/bin/domain/imag-diary/src/main.rs @@ -48,6 +48,7 @@ extern crate libimagtimeui; extern crate libimagutil; use libimagrt::setup::generate_runtime_setup; +use libimagerror::trace::MapErrTrace; mod create; mod delete; @@ -80,8 +81,12 @@ fn main() { "edit" => edit(&rt), "list" => list(&rt), "view" => view(&rt), - _ => { - debug!("Unknown command"); // More error handling + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-diary", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(std::process::exit); }, } }); diff --git a/bin/domain/imag-habit/src/main.rs b/bin/domain/imag-habit/src/main.rs index ccb8e71e..61b69bd6 100644 --- a/bin/domain/imag-habit/src/main.rs +++ b/bin/domain/imag-habit/src/main.rs @@ -90,9 +90,12 @@ fn main() { "status" => today(&rt, true), "show" => show(&rt), "done" => done(&rt), - _ => { - debug!("Unknown command"); // More error handling - exit(1) + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-habit", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(::std::process::exit); }, } }) diff --git a/bin/domain/imag-log/src/main.rs b/bin/domain/imag-log/src/main.rs index 7c2804ec..bc5a6a74 100644 --- a/bin/domain/imag-log/src/main.rs +++ b/bin/domain/imag-log/src/main.rs @@ -72,9 +72,12 @@ fn main() { if let Some(scmd) = rt.cli() .subcommand_name() { match scmd { "show" => show(&rt), - _ => { - error!("Unknown command"); - ::std::process::exit(1) + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-log", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(std::process::exit); }, } } else { diff --git a/bin/domain/imag-mail/src/main.rs b/bin/domain/imag-mail/src/main.rs index a108d692..b7d67f80 100644 --- a/bin/domain/imag-mail/src/main.rs +++ b/bin/domain/imag-mail/src/main.rs @@ -54,7 +54,13 @@ fn main() { "import-mail" => import_mail(&rt), "list" => list(&rt), "mail-store" => mail_store(&rt), - _ => debug!("Unknown command") // More error handling + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-mail", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(std::process::exit); + } } }); } diff --git a/bin/domain/imag-notes/src/main.rs b/bin/domain/imag-notes/src/main.rs index de395c54..6d193b4c 100644 --- a/bin/domain/imag-notes/src/main.rs +++ b/bin/domain/imag-notes/src/main.rs @@ -66,8 +66,12 @@ fn main() { "delete" => delete(&rt), "edit" => edit(&rt), "list" => list(&rt), - _ => { - debug!("Unknown command"); // More error handling + other => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-notes", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(std::process::exit); }, }; }); diff --git a/bin/domain/imag-timetrack/src/main.rs b/bin/domain/imag-timetrack/src/main.rs index ef857d54..2f432452 100644 --- a/bin/domain/imag-timetrack/src/main.rs +++ b/bin/domain/imag-timetrack/src/main.rs @@ -56,6 +56,7 @@ use week::week; use year::year; use libimagrt::setup::generate_runtime_setup; +use libimagerror::trace::MapErrTrace; fn main() { let version = make_imag_version!(); @@ -77,9 +78,12 @@ fn main() { "track" => track(&rt), "week" => week(&rt), "year" => year(&rt), - _ => { - error!("Unknown command"); - 1 + other => { + debug!("Unknown command"); + rt.handle_unknown_subcommand("imag-timetrack", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .unwrap_or(0) }, } } else { diff --git a/bin/domain/imag-todo/src/main.rs b/bin/domain/imag-todo/src/main.rs index 7eaf1d80..22ae1ede 100644 --- a/bin/domain/imag-todo/src/main.rs +++ b/bin/domain/imag-todo/src/main.rs @@ -51,10 +51,16 @@ fn main() { match rt.cli().subcommand_name() { Some("tw-hook") => tw_hook(&rt), Some("list") => list(&rt), + Some(other) => { + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-todo", other, rt.cli()) + .map_err_trace_exit_unwrap(1) + .code() + .map(std::process::exit); + } None => { warn!("No command"); }, - _ => unreachable!(), } // end match scmd } // end main diff --git a/doc/src/09020-changelog.md b/doc/src/09020-changelog.md index 93d16341..f4b86427 100644 --- a/doc/src/09020-changelog.md +++ b/doc/src/09020-changelog.md @@ -45,6 +45,9 @@ This section contains the changelog from the last release to the next release. commandline flag. * `imag-habit today --done` and `imag-habit status --done` was added for showing habits which are already done. + * `libimagrt` allows external subcommands now in the default clap app + builder helper. It also provides a helper for handling unknown + subcommands: `Runtime::handle_unknown_subcommand()`. See docs for details. * Minor changes * A license-checker was included into the CI setup, which checks whether all ".rs"-files have the license header at the top of the file diff --git a/lib/core/libimagrt/src/runtime.rs b/lib/core/libimagrt/src/runtime.rs index 8cb4521c..07b24bd4 100644 --- a/lib/core/libimagrt/src/runtime.rs +++ b/lib/core/libimagrt/src/runtime.rs @@ -24,6 +24,7 @@ use std::process::exit; use std::io::Stdin; pub use clap::App; +use clap::AppSettings; use toml::Value; use toml_query::read::TomlValueReadExt; @@ -189,6 +190,7 @@ impl<'a> Runtime<'a> { .version(version) .author("Matthias Beyer ") .about(about) + .settings(&[AppSettings::AllowExternalSubcommands]) .arg(Arg::with_name(Runtime::arg_verbosity_name()) .short("v") .long("verbose") @@ -502,6 +504,81 @@ impl<'a> Runtime<'a> { Some(::std::io::stdin()) } } + + /// Helper for handling subcommands which are not available. + /// + /// # Example + /// + /// For example someone calls `imag foo bar`. If `imag-foo` is in the $PATH, but it has no + /// subcommand `bar`, the `imag-foo` binary is able to automatically forward the invokation to a + /// `imag-foo-bar` binary which might be in $PATH. + /// + /// It needs to call `Runtime::handle_unknown_subcommand` with the following parameters: + /// + /// 1. The "command" which was issued. In the example this would be `"imag-foo"` + /// 2. The "subcommand" which is missing: `"bar"` in the example + /// 3. The `ArgMatches` object from the call, so that this routine can forward all flags passed + /// to the `bar` subcommand. + /// + /// # Return value + /// + /// On success, the exit status object of the `Command` invocation is returned. + /// On Error, a RuntimeError object is returned. + /// + /// # Details + /// + /// The `IMAG_RTP` variable is set for the child process. It is set to the current runtime path. + /// + /// Stdin, stdout and stderr are inherited to the child process. + /// + /// This function **blocks** until the child returns. + /// + pub fn handle_unknown_subcommand>(&self, + command: S, + subcommand: S, + args: &ArgMatches) + -> Result<::std::process::ExitStatus, RuntimeError> + { + use std::io::Write; + use std::io::ErrorKind; + + let rtp_str = self.rtp() + .to_str() + .map(String::from) + .ok_or(RuntimeErrorKind::IOError) + .map_err(RuntimeError::from_kind)?; + + let command = format!("{}-{}", command.as_ref(), subcommand.as_ref()); + + let subcommand_args = args.values_of("") + .map(|sx| sx.map(String::from).collect()) + .unwrap_or_else(|| vec![]); + + Command::new(&command) + .stdin(::std::process::Stdio::inherit()) + .stdout(::std::process::Stdio::inherit()) + .stderr(::std::process::Stdio::inherit()) + .args(&subcommand_args[..]) + .env("IMAG_RTP", rtp_str) + .spawn() + .and_then(|mut c| c.wait()) + .map_err(|e| match e.kind() { + ErrorKind::NotFound => { + let mut out = self.stdout(); + + if let Err(e) = writeln!(out, "No such command: '{}'", command) { + return e; + } + if let Err(e) = writeln!(out, "See 'imag --help' for available subcommands") { + return e; + } + + e + }, + _ => e, + }) + .map_err(RuntimeError::from) + } } /// Exported for the `imag` command, you probably do not want to use that.