Skip to main content

gbm/
cmds.rs

1use rootcause::report;
2use ytil_sys::cli::Args;
3
4pub mod init;
5pub mod install;
6pub mod pick;
7pub mod rename;
8
9const ZSHRC_INSTALL_LINE: &str = r#"(( $+commands[gbm] )) && eval "$(gbm init zsh)""#;
10const ZSH_WRAPPER: &str = r#"gbm() {
11  if (( $# == 0 )); then
12    local branch
13    branch="$(command gbm --pick)" || return
14    [[ -n "$branch" ]] || return
15    print -z -- "gbm ${(q)branch}"
16    return
17  fi
18
19  command gbm "$@"
20}
21"#;
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum Help {
25    Root,
26    Install,
27    Init,
28    Pick,
29}
30
31impl Help {
32    pub(crate) fn from_args(args: &[String]) -> Self {
33        match args.first().map(String::as_str) {
34            Some("install") => Self::Install,
35            Some("init") => Self::Init,
36            Some("--pick") => Self::Pick,
37            _ => Self::Root,
38        }
39    }
40
41    pub const fn text(self) -> &'static str {
42        match self {
43            Self::Root => include_str!("../help.txt"),
44            Self::Install => include_str!("../help/install/help.txt"),
45            Self::Init => include_str!("../help/init/help.txt"),
46            Self::Pick => include_str!("../help/pick/help.txt"),
47        }
48    }
49}
50
51#[derive(Debug, Eq, PartialEq)]
52pub enum Cmd {
53    Help(Help),
54    Pick,
55    Install,
56    InitZsh,
57    Rename(String),
58}
59
60impl Cmd {
61    pub fn from_env() -> rootcause::Result<Self> {
62        let args = ytil_sys::cli::get();
63        let help = Help::from_args(&args);
64        Self::try_from(args).inspect_err(|_| eprintln!("{}", help.text()))
65    }
66}
67
68impl TryFrom<Vec<String>> for Cmd {
69    type Error = rootcause::Report;
70
71    fn try_from(args: Vec<String>) -> Result<Self, Self::Error> {
72        if args.has_help() {
73            return Ok(Self::Help(Help::from_args(&args)));
74        }
75
76        match args.as_slice() {
77            [] => Err(report!("gbm shell wrapper is not installed or loaded")
78                .attach("run `gbm install` first, then restart zsh or source ~/.zshrc")),
79            [argument] if argument == "--pick" => Ok(Self::Pick),
80            [argument] if argument == "install" => Ok(Self::Install),
81            [argument] if argument == "init" => Err(report!("missing gbm init shell")),
82            [first, second] if first == "init" && second == "zsh" => Ok(Self::InitZsh),
83            [first, ..] if first == "init" => Err(report!("unsupported gbm init shell")),
84            [branch_name] => Ok(Self::Rename(branch_name.clone())),
85            _ => Ok(Self::Rename(args.join("-"))),
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use test_that::prelude::*;
93
94    use super::*;
95
96    #[rstest::rstest]
97    #[case::help(vec!["--help"], Cmd::Help(Help::Root))]
98    #[case::pick(vec!["--pick"], Cmd::Pick)]
99    #[case::install(vec!["install"], Cmd::Install)]
100    #[case::init_zsh(vec!["init", "zsh"], Cmd::InitZsh)]
101    #[case::rename(vec!["feature"], Cmd::Rename("feature".to_owned()))]
102    #[case::join_rename_parts(
103        vec!["feature", "one"],
104        Cmd::Rename("feature-one".to_owned())
105    )]
106    fn test_parse_known_commands_returns_expected_command(#[case] args: Vec<&str>, #[case] expected: Cmd) {
107        assert_that!(parse(args), ok(eq(expected)));
108    }
109
110    #[test]
111    fn test_parse_without_arguments_returns_wrapper_error() {
112        assert_that!(Cmd::try_from(Vec::new()), err(anything()));
113    }
114
115    #[rstest::rstest]
116    #[case::root(vec!["--help"], Help::Root)]
117    #[case::install(vec!["install", "--help"], Help::Install)]
118    #[case::init(vec!["init", "--help"], Help::Init)]
119    #[case::pick(vec!["--pick", "--help"], Help::Pick)]
120    fn test_help_when_command_varies_selects_the_matching_command(#[case] args: Vec<&str>, #[case] expected: Help) {
121        let args = args.into_iter().map(ToOwned::to_owned).collect::<Vec<_>>();
122        assert_that!(Help::from_args(&args), eq(expected));
123    }
124
125    fn parse(args: Vec<&str>) -> rootcause::Result<Cmd> {
126        Cmd::try_from(args.into_iter().map(ToOwned::to_owned).collect::<Vec<String>>())
127    }
128}