Skip to main content

evoke/
cmds.rs

1use rootcause::report;
2use ytil_sys::cli::Args;
3
4pub mod ci;
5pub mod local;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum Help {
9    Root,
10    Ci,
11    CiAll,
12    CiLint,
13    CiTest,
14    CiReleaseNative,
15    CiAudit,
16}
17
18impl Help {
19    pub(crate) fn from_args(args: &[String]) -> Self {
20        if args.first().is_none_or(|arg| arg != "ci") {
21            return Self::Root;
22        }
23
24        match args.get(1).map(String::as_str) {
25            Some("all") => Self::CiAll,
26            Some("lint") => Self::CiLint,
27            Some("test") => Self::CiTest,
28            Some("release-native") => Self::CiReleaseNative,
29            Some("audit") => Self::CiAudit,
30            _ => Self::Ci,
31        }
32    }
33
34    pub const fn text(self) -> &'static str {
35        match self {
36            Self::Root => include_str!("../help.txt"),
37            Self::Ci => include_str!("../help/ci/help.txt"),
38            Self::CiAll => include_str!("../help/ci/all/help.txt"),
39            Self::CiLint => include_str!("../help/ci/lint/help.txt"),
40            Self::CiTest => include_str!("../help/ci/test/help.txt"),
41            Self::CiReleaseNative => include_str!("../help/ci/release-native/help.txt"),
42            Self::CiAudit => include_str!("../help/ci/audit/help.txt"),
43        }
44    }
45}
46
47#[derive(Debug, Eq, PartialEq)]
48pub enum Cmd {
49    Help(Help),
50    Ci(ci::CmdKind),
51    Local(Vec<String>),
52}
53
54impl Cmd {
55    pub fn from_env() -> rootcause::Result<Self> {
56        let args = ytil_sys::cli::get();
57        let help = Help::from_args(&args);
58        Self::try_from(args).inspect_err(|_| eprintln!("{}", help.text()))
59    }
60}
61
62impl TryFrom<Vec<String>> for Cmd {
63    type Error = rootcause::Report;
64
65    fn try_from(args: Vec<String>) -> Result<Self, Self::Error> {
66        if args.has_help() {
67            return Ok(Self::Help(Help::from_args(&args)));
68        }
69
70        if args.first().is_some_and(|arg| arg == "ci") {
71            let command = ci::cmd_from_args(&args)?.ok_or_else(|| report!("missing evoke ci command"))?;
72            return Ok(Self::Ci(command));
73        }
74
75        Ok(Self::Local(args))
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use test_that::prelude::*;
82
83    use super::*;
84
85    #[rstest::rstest]
86    #[case::help(vec!["--help"], Cmd::Help(Help::Root))]
87    #[case::local(vec!["--debug"], Cmd::Local(vec!["--debug".to_owned()]))]
88    #[case::ci_default(vec!["ci"], Cmd::Ci(ci::CmdKind::All))]
89    #[case::ci_lint(vec!["ci", "lint"], Cmd::Ci(ci::CmdKind::Lint))]
90    fn test_parse_known_commands_returns_expected_command(#[case] args: Vec<&str>, #[case] expected: Cmd) {
91        assert_that!(parse(args), ok(eq(expected)));
92    }
93
94    #[rstest::rstest]
95    #[case::ci_extra_arg(vec!["ci", "lint", "extra"])]
96    #[case::ci_unknown_command(vec!["ci", "unknown"])]
97    fn test_parse_invalid_ci_commands_returns_error(#[case] args: Vec<&str>) {
98        assert_that!(parse(args), err(anything()));
99    }
100
101    #[rstest::rstest]
102    #[case::root(vec!["--help"], Help::Root)]
103    #[case::ci(vec!["ci", "--help"], Help::Ci)]
104    #[case::ci_lint(vec!["ci", "lint", "--help"], Help::CiLint)]
105    fn test_help_when_command_path_varies_selects_the_deepest_command(#[case] args: Vec<&str>, #[case] expected: Help) {
106        let args = args.into_iter().map(ToOwned::to_owned).collect::<Vec<_>>();
107        assert_that!(Help::from_args(&args), eq(expected));
108    }
109
110    fn parse(args: Vec<&str>) -> rootcause::Result<Cmd> {
111        Cmd::try_from(args.into_iter().map(ToOwned::to_owned).collect::<Vec<String>>())
112    }
113}