1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use std::{ffi::OsString, fmt, process};

use dialoguer::console::style;

mod args;
mod error;

pub use args::Args;
pub use error::Error;

pub trait Command<A, Ctx>
where
    A: Args,
{
    fn run(self, ctx: Ctx, args: A) -> eyre::Result<()>;
}

impl<F, A, Ctx> Command<A, Ctx> for F
where
    A: Args,
    F: FnOnce(Ctx, A) -> eyre::Result<()>,
{
    fn run(self, ctx: Ctx, args: A) -> eyre::Result<()> {
        self(ctx, args)
    }
}

pub struct Help {
    pub name: &'static str,
    pub description: &'static str,
    pub version: &'static str,
    pub usage: &'static str,
}

pub fn error(err: impl fmt::Display) {
    println!("{} {}", style("==").red(), style(err).red());
}

pub fn run<A, Ctx, C>(ctx: Ctx, help: Help, cmd: C, args: Vec<OsString>) -> !
where
    A: Args,
    C: Command<A, Ctx>,
{
    let options = match A::from_args(args) {
        Ok(opts) => opts,
        Err(err) => {
            match err.downcast_ref::<Error>() {
                Some(Error::Help) => {
                    println!(
                        "{} {}\n{}\n{}",
                        help.name, help.version, help.description, help.usage
                    );
                    process::exit(0);
                },
                Some(Error::Usage) => {
                    println!(
                        "{}\n{}",
                        style(format!("Error: {} invalid usage", help.name)).red(),
                        style(help.usage).red().dim()
                    );
                    process::exit(1);
                },
                _ => {},
            };

            fail(help.name, &err);
        },
    };

    match cmd.run(ctx, options) {
        Ok(()) => process::exit(0),
        Err(err) => {
            fail(&format!("{} failed", help.name), &err);
        },
    }
}

fn fail(name: &str, err: &eyre::Report) -> ! {
    eprintln!(
        "{} {} {} {}",
        style("==").red(),
        style("Error").red(),
        style(format!("{}:", name)).red(),
        style(err.to_string()).red(),
    );

    if let Some(Error::Hint { hint, .. }) = err.downcast_ref::<Error>() {
        eprintln!("{}", style(hint).yellow());
    }

    process::exit(1);
}