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}