1use std::ffi::OsString;
2
3use ytil_sys::cli::Args;
4use ytil_sys::pico_args::Arguments;
5
6pub mod branch;
7pub mod issue;
8pub mod list;
9pub mod pr;
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum Help {
13 Root,
14 List,
15 Issue,
16 Pr,
17 Branch,
18}
19
20impl Help {
21 pub(crate) fn from_args(args: &[OsString]) -> Self {
22 if has_command(args, "issue") {
23 return Self::Issue;
24 }
25 if has_command(args, "pr") {
26 return Self::Pr;
27 }
28 if has_command(args, "branch") {
29 return Self::Branch;
30 }
31 if args.is_empty() || args.iter().all(|arg| arg == "--help") {
32 Self::Root
33 } else {
34 Self::List
35 }
36 }
37
38 pub const fn text(self) -> &'static str {
39 match self {
40 Self::Root => include_str!("../help.txt"),
41 Self::List => include_str!("../help/list/help.txt"),
42 Self::Issue => include_str!("../help/issue/help.txt"),
43 Self::Pr => include_str!("../help/pr/help.txt"),
44 Self::Branch => include_str!("../help/branch/help.txt"),
45 }
46 }
47}
48
49pub enum Cmd {
50 Help(Help),
51 List(Arguments),
52 Issue,
53 Pr,
54 Branch,
55}
56
57impl Cmd {
58 pub fn from_env() -> rootcause::Result<Self> {
59 let args = Arguments::from_env();
60 let help = Help::from_args(&args.clone().finish());
61 Self::try_from(args).inspect_err(|_| eprintln!("{}", help.text()))
62 }
63}
64
65impl TryFrom<Arguments> for Cmd {
66 type Error = rootcause::Report;
67
68 fn try_from(args: Arguments) -> Result<Self, Self::Error> {
69 let raw_args = args.clone().finish();
70 if args.has_help() {
71 return Ok(Self::Help(Help::from_args(&raw_args)));
72 }
73
74 if has_command(&raw_args, "issue") {
75 return Ok(Self::Issue);
76 }
77
78 if has_command(&raw_args, "pr") {
79 return Ok(Self::Pr);
80 }
81
82 if has_command(&raw_args, "branch") {
83 return Ok(Self::Branch);
84 }
85
86 Ok(Self::List(args))
87 }
88}
89
90fn has_command(args: &[OsString], command: &str) -> bool {
91 let mut skip_next = false;
92 for argument in args {
93 if skip_next {
94 skip_next = false;
95 continue;
96 }
97
98 let argument = argument.to_string_lossy();
99 if matches!(argument.as_ref(), "--search" | "--merge-state") {
100 skip_next = true;
101 continue;
102 }
103 if argument.starts_with("--search=") || argument.starts_with("--merge-state=") {
104 continue;
105 }
106 if argument == command {
107 return true;
108 }
109 }
110
111 false
112}
113
114#[cfg(test)]
115mod tests {
116 use std::ffi::OsString;
117
118 use test_that::prelude::*;
119 use ytil_sys::pico_args::Arguments;
120
121 use super::*;
122
123 #[test]
124 fn test_parse_when_help_is_requested_returns_help() {
125 assert!(matches!(parse(&["--help"]), Ok(Cmd::Help(Help::Root))));
126 }
127
128 #[rstest::rstest]
129 #[case::issue("issue", "issue")]
130 #[case::pull_request("pr", "pr")]
131 #[case::branch("branch", "branch")]
132 fn test_parse_when_named_command_is_supplied_returns_command(#[case] command: &str, #[case] expected: &str) {
133 let parsed = parse(&[command]);
134 assert!(matches!(
135 (expected, parsed),
136 ("issue", Ok(Cmd::Issue)) | ("pr", Ok(Cmd::Pr)) | ("branch", Ok(Cmd::Branch))
137 ));
138 }
139
140 #[test]
141 fn test_parse_when_no_named_command_is_supplied_returns_list_command() {
142 assert!(matches!(parse(&["--search", "lint"]), Ok(Cmd::List(_))));
143 }
144
145 #[test]
146 fn test_parse_when_command_name_is_search_value_returns_list_command() {
147 assert!(matches!(parse(&["--search", "issue"]), Ok(Cmd::List(_))));
148 }
149
150 #[rstest::rstest]
151 #[case::root(&["--help"], Help::Root)]
152 #[case::list(&["--search", "lint", "--help"], Help::List)]
153 #[case::issue(&["issue", "--help"], Help::Issue)]
154 #[case::pull_request(&["pr", "--help"], Help::Pr)]
155 #[case::branch(&["branch", "--help"], Help::Branch)]
156 fn test_help_when_command_varies_selects_the_matching_command(#[case] raw: &[&str], #[case] expected: Help) {
157 let args = raw.iter().map(OsString::from).collect::<Vec<_>>();
158 assert_that!(Help::from_args(&args), eq(expected));
159 }
160
161 fn parse(args: &[&str]) -> rootcause::Result<Cmd> {
162 Cmd::try_from(Arguments::from_vec(args.iter().map(OsString::from).collect()))
163 }
164}