141 lines
3.9 KiB
Rust
141 lines
3.9 KiB
Rust
|
#![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<String> {
|
||
|
//! Some(String::from("<param1> <param2>"))
|
||
|
//! }
|
||
|
//!
|
||
|
//! fn validate_args(&self, mut args: impl Iterator<Item = String>) -> keyfork_bin::ProcessResult<Self::Args> {
|
||
|
//! 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<T = ()> = Result<T, Box<dyn std::error::Error>>;
|
||
|
|
||
|
fn report_err(e: Box<dyn std::error::Error>) {
|
||
|
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<String> {
|
||
|
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<Item = String>) -> ProcessResult<Self::Args>;
|
||
|
|
||
|
/// 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<Item = String>) -> 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<F: Fn() -> ProcessResult> {
|
||
|
closure: F
|
||
|
}
|
||
|
|
||
|
impl<F> ClosureBin<F> 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<F> Bin for ClosureBin<F> where F: Fn() -> ProcessResult {
|
||
|
type Args = ();
|
||
|
|
||
|
fn validate_args(&self, _args: impl Iterator<Item = String>) -> ProcessResult<Self::Args> {
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn run(&self, _args: Self::Args) -> ProcessResult {
|
||
|
let c = &self.closure;
|
||
|
c()
|
||
|
}
|
||
|
}
|