radicle-heartwood-lfs/radicle-cli/src/terminal.rs

144 lines
3.7 KiB
Rust

pub mod args;
pub use args::{Args, Error, Help};
pub mod cob;
pub mod format;
pub mod io;
pub use io::{proposal, signer};
pub mod patch;
pub use radicle_term::*;
use std::ffi::OsString;
use std::process;
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<Profile, anyhow::Error>;
}
impl Context for Profile {
fn profile(&self) -> Result<Profile, anyhow::Error> {
Ok(self.clone())
}
}
impl<F> Context for F
where
F: Fn() -> Result<Profile, anyhow::Error>,
{
fn profile(&self) -> Result<Profile, anyhow::Error> {
self()
}
}
/// A command that can be run.
pub trait Command<A: Args, C: Context> {
/// Run the command, given arguments and a context.
fn run(self, args: A, context: C) -> anyhow::Result<()>;
}
impl<F, A: Args, C: Context> Command<A, C> 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<A, C>(help: Help, action: &str, cmd: C) -> !
where
A: Args,
C: Command<A, fn() -> anyhow::Result<Profile>>,
{
let args = std::env::args_os().into_iter().skip(1).collect();
run_command_args(help, action, cmd, args)
}
pub fn run_command_args<A, C>(help: Help, action: &str, cmd: C, args: Vec<OsString>) -> !
where
A: Args,
C: Command<A, fn() -> anyhow::Result<Profile>>,
{
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::<Error>() {
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!(
"{} {} {} {}",
Paint::red("=="),
Paint::red("Error:"),
Paint::red(format!("rad-{}:", help.name)),
Paint::red(err.to_string()),
);
if let Some(Error::WithHint { hint, .. }) = err.downcast_ref::<Error>() {
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<Profile, anyhow::Error> {
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::<Error>() {
println!("{} {}", ERROR_HINT_PREFIX, Paint::yellow(hint));
blank();
}
}