#![allow(clippy::needless_doctest_main)] //! A convenient trait for quickly writing binaries in a consistent pattern. //! //! # Examples //! ```rust //! use anyhow::anyhow; //! use keyfork_bin::Bin; //! //! struct Main; //! //! impl Bin for Main { //! type Args = (String, String); //! //! fn usage_hint(&self) -> Option { //! Some(String::from(" ")) //! } //! //! fn validate_args(&self, mut args: impl Iterator) -> keyfork_bin::ProcessResult { //! let arg1 = args.next().ok_or(anyhow!("missing argument 1"))?; //! let arg2 = args.next().ok_or(anyhow!("missing argument 2"))?; //! Ok((arg1, arg2)) //! } //! //! fn run(&self, (arg1, arg2): Self::Args) -> keyfork_bin::ProcessResult { //! println!("First argument: {arg1}"); //! println!("Second argument: {arg2}"); //! Ok(()) //! } //!# //!# fn main(&self) -> std::process::ExitCode { //!# self.main_inner([String::from("hello"), String::from("world")].into_iter()) //!# } //! } //! //! fn main() { //! // Assume the program was called with something like "hello world"... //! let bin = Main; //! bin.main(); //! } //! ``` use std::process::ExitCode; /// A result that may contain any error. pub type ProcessResult = Result>; fn report_err(e: Box) { eprintln!("Unable to run command: {e}"); let mut source = e.source(); while let Some(new_error) = source.take() { eprintln!("- Caused by: {new_error}"); source = new_error.source(); } } /// A trait for implementing the flow of a binary's execution. pub trait Bin { /// The type for command-line arguments required by the function. type Args; /// A usage hint for how the arguments should be provided to the program. fn usage_hint(&self) -> Option { None } /// Validate the arguments provided by the user into types required by the binary. #[allow(clippy::missing_errors_doc)] fn validate_args(&self, args: impl Iterator) -> ProcessResult; /// Run the binary #[allow(clippy::missing_errors_doc)] fn run(&self, args: Self::Args) -> ProcessResult; /// The default handler for running the binary and reporting any errors. fn main(&self) -> ExitCode { self.main_inner(std::env::args()) } #[doc(hidden)] fn main_inner(&self, mut args: impl Iterator) -> ExitCode { let command = args.next(); let args = match self.validate_args(args) { Ok(args) => args, Err(e) => { if let (Some(command), Some(hint)) = (command, self.usage_hint()) { eprintln!("Usage: {command} {hint}"); } report_err(e); return ExitCode::FAILURE; } }; if let Err(e) = self.run(args) { report_err(e); return ExitCode::FAILURE; } ExitCode::SUCCESS } } /// A Bin that doesn't take any arguments. pub struct ClosureBin ProcessResult> { closure: F } impl ClosureBin where F: Fn() -> ProcessResult { /// Create a new Bin from a closure. /// /// # Examples /// ```rust /// use keyfork_bin::{Bin, ClosureBin}; /// /// let bin = ClosureBin::new(|| { /// println!("Hello, world!"); /// Ok(()) /// }); /// /// bin.main(); /// ``` pub fn new(closure: F) -> Self { Self { closure } } } impl Bin for ClosureBin where F: Fn() -> ProcessResult { type Args = (); fn validate_args(&self, _args: impl Iterator) -> ProcessResult { Ok(()) } fn run(&self, _args: Self::Args) -> ProcessResult { let c = &self.closure; c() } }