pub mod args; pub mod command; pub mod format; pub mod io; pub mod patch; pub mod spinner; pub mod table; pub mod textbox; use std::ffi::OsString; use std::process; use dialoguer::console::style; use radicle::profile::Profile; pub use args::{Args, Error, Help}; pub use console::measure_text_width as text_width; pub use dialoguer::Editor; pub use io::*; pub use spinner::{spinner, Spinner}; pub use table::Table; pub use textbox::TextBox; /// 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!( "{} {} {} {}", style("==").red(), style("Error:").red(), style(format!("rad-{}:", help.name)).red(), style(&err).red() ); if let Some(Error::WithHint { hint, .. }) = err.downcast_ref::() { eprintln!("{}", style(hint).yellow()); } process::exit(1); } }; match cmd.run(options, self::profile) { Ok(()) => process::exit(0), Err(err) => { term::fail(&format!("{} failed", action), &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()), } } #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Interactive { Yes, No, } impl Default for Interactive { fn default() -> Self { Interactive::No } } impl Interactive { pub fn yes(&self) -> bool { (*self).into() } pub fn no(&self) -> bool { !self.yes() } } impl From for bool { fn from(c: Interactive) -> Self { match c { Interactive::Yes => true, Interactive::No => false, } } } impl From for Interactive { fn from(b: bool) -> Self { if b { Interactive::Yes } else { Interactive::No } } }