pub mod args; pub use args::{Args, Error, Help}; pub mod format; pub mod io; pub use io::{proposal, signer}; pub mod patch; use std::ffi::OsString; use std::process; pub use radicle_term::*; use radicle::profile::Profile; use crate::terminal; /// Context passed to all commands. pub trait Context { /// Return the currently active profile, or an error if no profile is active. fn profile(&self) -> Result; } impl Context for Profile { fn profile(&self) -> Result { Ok(self.clone()) } } impl Context for F where F: Fn() -> Result, { fn profile(&self) -> Result { self() } } /// A command that can be run. pub trait Command { /// Run the command, given arguments and a context. fn run(self, args: A, context: C) -> anyhow::Result<()>; } impl Command for F where F: FnOnce(A, C) -> anyhow::Result<()>, { fn run(self, args: A, context: C) -> anyhow::Result<()> { self(args, context) } } pub fn run_command(help: Help, action: &str, cmd: C) -> ! where A: Args, C: Command anyhow::Result>, { let args = std::env::args_os().into_iter().skip(1).collect(); run_command_args(help, action, cmd, args) } pub fn run_command_args(help: Help, action: &str, cmd: C, args: Vec) -> ! where A: Args, C: Command anyhow::Result>, { use io as term; let options = match A::from_args(args) { Ok((opts, unparsed)) => { if let Err(err) = args::finish(unparsed) { term::error(err); process::exit(1); } opts } Err(err) => { match err.downcast_ref::() { Some(Error::Help) => { term::help(help.name, help.version, help.description, help.usage); process::exit(0); } Some(Error::Usage) => { term::usage(help.name, help.usage); process::exit(1); } _ => {} }; eprintln!( "{} {} rad {}: {err}", Paint::red(ERROR_PREFIX), Paint::red("Error:"), help.name, ); if let Some(Error::WithHint { hint, .. }) = err.downcast_ref::() { eprintln!("{}", Paint::yellow(hint)); } process::exit(1); } }; match cmd.run(options, self::profile) { Ok(()) => process::exit(0), Err(err) => { terminal::fail(&format!("{action} failed"), &err); process::exit(1); } } } /// Get the default profile. Fails if there is no profile. pub fn profile() -> Result { let error = args::Error::WithHint { err: anyhow::anyhow!("Could not load radicle profile"), hint: "To setup your radicle profile, run `rad auth`.", }; match Profile::load() { Ok(profile) => Ok(profile), Err(_) => Err(error.into()), } } pub fn fail(header: &str, error: &anyhow::Error) { let err = error.to_string(); let err = err.trim_end(); let separator = if err.contains('\n') { ":\n" } else { ": " }; println!( "{ERROR_PREFIX} {}{}{error:#}", Paint::red(header).bold(), Paint::red(separator), ); if let Some(Error::WithHint { hint, .. }) = error.downcast_ref::() { println!("{} {}", ERROR_HINT_PREFIX, Paint::yellow(hint)); blank(); } }