Skip to main content

frs/
cmds.rs

1use std::ffi::OsString;
2
3use rootcause::report;
4use ytil_sys::pico_args::Arguments;
5
6pub mod repo;
7pub mod rsl;
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum Help {
11    Root,
12    Repo,
13    RepoFix,
14    Rsl,
15}
16
17impl Help {
18    pub(crate) fn from_args(args: &[OsString]) -> Self {
19        match args.first().map(|arg| arg.to_string_lossy()).as_deref() {
20            Some("repo") if args.get(1).is_some_and(|arg| arg == "fix") => Self::RepoFix,
21            Some("repo") => Self::Repo,
22            Some("rsl") => Self::Rsl,
23            _ => Self::Root,
24        }
25    }
26
27    pub const fn text(self) -> &'static str {
28        match self {
29            Self::Root => include_str!("../help.txt"),
30            Self::Repo => include_str!("../help/repo/help.txt"),
31            Self::RepoFix => include_str!("../help/repo/fix/help.txt"),
32            Self::Rsl => include_str!("../help/rsl/help.txt"),
33        }
34    }
35}
36
37#[derive(Debug, Eq, PartialEq)]
38pub enum Cmd {
39    Help(Help),
40    Repo(Vec<OsString>),
41    Rsl(Vec<OsString>),
42}
43
44impl Cmd {
45    pub fn from_env() -> rootcause::Result<Self> {
46        let args = Arguments::from_env();
47        let help = Help::from_args(&args.clone().finish());
48        Self::try_from(args).inspect_err(|_| eprintln!("{}", help.text()))
49    }
50}
51
52impl TryFrom<Arguments> for Cmd {
53    type Error = rootcause::Report;
54
55    fn try_from(mut args: Arguments) -> Result<Self, Self::Error> {
56        let Some(command) = args.subcommand()? else {
57            return if args.contains("--help") || args.finish().is_empty() {
58                Ok(Self::Help(Help::Root))
59            } else {
60                Err(report!("unsupported frs command"))
61            };
62        };
63
64        if args.clone().contains("--help") {
65            let raw_args = args.clone().finish();
66            let full_args = std::iter::once(OsString::from(command.as_str()))
67                .chain(raw_args)
68                .collect::<Vec<_>>();
69            return Ok(Self::Help(Help::from_args(&full_args)));
70        }
71
72        let remaining = args.finish();
73        match command.as_str() {
74            "repo" => Ok(Self::Repo(remaining)),
75            "rsl" => Ok(Self::Rsl(remaining)),
76            command => Err(report!("unsupported frs command").attach(format!("command={command}"))),
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use std::ffi::OsString;
84
85    use test_that::prelude::*;
86    use ytil_sys::pico_args::Arguments;
87
88    use super::*;
89
90    #[rstest::rstest]
91    #[case::bare(&[], Cmd::Help(Help::Root))]
92    #[case::help(&["--help"], Cmd::Help(Help::Root))]
93    #[case::repo(&["repo"], Cmd::Repo(Vec::new()))]
94    #[case::repo_fix(
95        &["repo", "fix", "--clean"],
96        Cmd::Repo(vec![OsString::from("fix"), OsString::from("--clean")])
97    )]
98    #[case::repo_help(&["repo", "--help"], Cmd::Help(Help::Repo))]
99    #[case::repo_fix_help(&["repo", "fix", "--help"], Cmd::Help(Help::RepoFix))]
100    #[case::rsl(&["rsl", "sample.rs"], Cmd::Rsl(vec![OsString::from("sample.rs")]))]
101    #[case::rsl_help(&["rsl", "--help"], Cmd::Help(Help::Rsl))]
102    fn test_parse_known_commands(#[case] args: &[&str], #[case] expected: Cmd) {
103        assert_that!(parse(args), ok(eq(expected)));
104    }
105
106    #[rstest::rstest]
107    #[case::unexpected_argument(&["unexpected"])]
108    #[case::unknown_command(&["unknown", "--value"])]
109    fn test_parse_rejects_invalid_commands(#[case] args: &[&str]) {
110        assert_that!(parse(args), err(anything()));
111    }
112
113    fn parse(args: &[&str]) -> rootcause::Result<Cmd> {
114        Cmd::try_from(Arguments::from_vec(args.iter().map(OsString::from).collect()))
115    }
116}