1use crate::printer::OutputFormatKind;
2
3#[derive(Debug, PartialEq)]
5pub struct Arguments {
6 pub repos: Vec<String>,
7 pub analysis: bool,
8 pub pagination: bool,
9 pub page_size: usize,
10 pub enable_line_editor: bool,
11 pub output_format: OutputFormatKind,
12}
13
14impl Arguments {
16 fn new() -> Arguments {
17 Arguments {
18 repos: vec![],
19 analysis: false,
20 pagination: false,
21 page_size: 10,
22 enable_line_editor: false,
23 output_format: OutputFormatKind::Table,
24 }
25 }
26}
27
28#[derive(Debug, PartialEq)]
29pub enum Command {
30 ReplMode(Arguments),
31 QueryMode(String, Arguments),
32 ScriptMode(String, Arguments),
33 Help,
34 Version,
35 Error(String),
36}
37
38pub fn parse_arguments(args: &[String]) -> Command {
39 let args_len = args.len();
40
41 if args.iter().any(|i| i == "--help" || i == "-h") {
42 return Command::Help;
43 }
44
45 if args.iter().any(|i| i == "--version" || i == "-v") {
46 return Command::Version;
47 }
48
49 let mut optional_query: Option<String> = None;
50 let mut optional_script_file: Option<String> = None;
51 let mut arguments = Arguments::new();
52
53 let mut arg_index = 1;
54 loop {
55 if arg_index >= args_len {
56 break;
57 }
58
59 let arg = &args[arg_index];
60 if !arg.starts_with('-') {
61 return Command::Error(format!("Unknown argument {}", arg));
62 }
63
64 match arg.as_ref() {
65 "--repos" | "-r" => {
66 arg_index += 1;
67 if arg_index >= args_len {
68 let message = format!("Argument {} must be followed by one or more path", arg);
69 return Command::Error(message);
70 }
71
72 loop {
73 if arg_index >= args_len {
74 break;
75 }
76
77 let repo = &args[arg_index];
78 if !repo.starts_with('-') {
79 arguments.repos.push(repo.to_string());
80 arg_index += 1;
81 continue;
82 }
83
84 break;
85 }
86 }
87 "--query" | "-q" => {
88 arg_index += 1;
89 if arg_index >= args_len {
90 let message = format!("Argument {} must be followed by the query", arg);
91 return Command::Error(message);
92 }
93
94 optional_query = Some(args[arg_index].to_string());
95 arg_index += 1;
96 }
97 "--script" | "-s" => {
98 arg_index += 1;
99 if arg_index >= args_len {
100 let message = format!("Argument {} must be followed by the file", arg);
101 return Command::Error(message);
102 }
103
104 optional_script_file = Some(args[arg_index].to_string());
105 arg_index += 1;
106 }
107 "--analysis" | "-a" => {
108 arguments.analysis = true;
109 arg_index += 1;
110 }
111 "--pagination" | "-p" => {
112 arguments.pagination = true;
113 arg_index += 1;
114 }
115 "--pagesize" | "-ps" => {
116 arg_index += 1;
117 if arg_index >= args_len {
118 let message = format!("Argument {} must be followed by the page size", arg);
119 return Command::Error(message);
120 }
121
122 let page_size_result = args[arg_index].parse::<usize>();
123 if page_size_result.is_err() {
124 return Command::Error("Invalid page size".to_string());
125 }
126
127 let page_size = page_size_result.ok().unwrap();
128 arguments.page_size = page_size;
129 arg_index += 1;
130 }
131 "--editor" | "-e" => {
132 arguments.enable_line_editor = true;
133 arg_index += 1;
134 }
135 "--output" | "-o" => {
136 arg_index += 1;
137 if arg_index >= args_len {
138 let message = format!("Argument {} must be followed by output format", arg);
139 return Command::Error(message);
140 }
141
142 let output_type = &args[arg_index].to_lowercase();
143 if output_type == "csv" {
144 arguments.output_format = OutputFormatKind::CSV;
145 } else if output_type == "json" {
146 arguments.output_format = OutputFormatKind::JSON;
147 } else if output_type == "table" || output_type == "render" {
148 arguments.output_format = OutputFormatKind::Table;
149 } else if output_type == "yaml" {
150 arguments.output_format = OutputFormatKind::YAML;
151 } else {
152 return Command::Error("Invalid output format".to_string());
153 }
154
155 arg_index += 1;
156 }
157 _ => return Command::Error(format!("Unknown command {}", arg)),
158 }
159 }
160
161 if arguments.repos.is_empty() {
163 let current_dir = std::env::current_dir();
164 if current_dir.is_ok() {
165 arguments.repos.push(
166 current_dir
167 .ok()
168 .unwrap()
169 .as_os_str()
170 .to_str()
171 .unwrap_or(".")
172 .to_string(),
173 );
174 } else {
175 return Command::Error("Missing repositories paths".to_string());
176 }
177 }
178
179 if let Some(script_file) = optional_script_file {
180 Command::ScriptMode(script_file, arguments)
181 } else if let Some(query) = optional_query {
182 Command::QueryMode(query, arguments)
183 } else {
184 Command::ReplMode(arguments)
185 }
186}
187
188pub fn print_help_list() {
189 println!("GitQL is a SQL like query language to run on local repositories");
190 println!();
191 println!("Usage: gitql [OPTIONS]");
192 println!();
193 println!("Options:");
194 println!("-r, --repos <REPOS> Path for local repositories to run query on");
195 println!("-s, --script <file> Script file contains one or more query");
196 println!("-q, --query <GitQL Query> GitQL query to run on selected repositories");
197 println!("-p, --pagination Enable print result with pagination");
198 println!("-ps, --pagesize Set pagination page size [default: 10]");
199 println!("-o, --output Set output format [render, json, csv]");
200 println!("-a, --analysis Print Query analysis");
201 println!("-e, --editor Enable GitQL Rich Line Editor");
202 println!("-h, --help Print GitQL help");
203 println!("-v, --version Print GitQL Current Version");
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_empty_arguments() {
212 let arguments = vec!["gitql".to_string()];
213 let command = parse_arguments(&arguments);
214 assert!(matches!(command, Command::ReplMode { .. }));
215 }
216
217 #[test]
218 fn test_repl_arguments() {
219 let arguments = vec!["gitql".to_string(), "--repos".to_string(), ".".to_string()];
220 let command = parse_arguments(&arguments);
221 assert!(matches!(command, Command::ReplMode { .. }));
222 }
223
224 #[test]
225 fn test_query_arguments() {
226 let arguments = vec![
227 "gitql".to_string(),
228 "-q".to_string(),
229 "Select * from table".to_string(),
230 ];
231 let command = parse_arguments(&arguments);
232 assert!(matches!(command, Command::QueryMode { .. }));
233 }
234
235 #[test]
236 fn test_arguments_with_help() {
237 let arguments = vec![
238 "gitql".to_string(),
239 "dummy".to_string(),
240 "--help".to_string(),
241 ];
242 let command = parse_arguments(&arguments);
243 assert_eq!(command, Command::Help);
244 }
245
246 #[test]
247 fn test_arguments_with_version() {
248 let arguments = vec![
249 "gitql".to_string(),
250 "dummy".to_string(),
251 "--version".to_string(),
252 ];
253 let command = parse_arguments(&arguments);
254 assert_eq!(command, Command::Version);
255 }
256
257 #[test]
258 fn test_arguments_with_valid_page_size() {
259 let arguments = vec![
260 "gitql".to_string(),
261 "--pagesize".to_string(),
262 "10".to_string(),
263 ];
264 let command = parse_arguments(&arguments);
265 assert!(!matches!(command, Command::Error { .. }));
266 }
267
268 #[test]
269 fn test_arguments_with_invalid_page_size() {
270 let arguments = vec![
271 "gitql".to_string(),
272 "--pagesize".to_string(),
273 "-".to_string(),
274 ];
275 let command = parse_arguments(&arguments);
276 assert!(matches!(command, Command::Error { .. }));
277 }
278
279 #[test]
280 fn test_arguments_with_valid_output_format() {
281 let arguments = vec![
282 "gitql".to_string(),
283 "--output".to_string(),
284 "csv".to_string(),
285 ];
286 let command = parse_arguments(&arguments);
287 assert!(!matches!(command, Command::Error { .. }));
288 }
289
290 #[test]
291 fn test_arguments_with_invalid_output_format() {
292 let arguments = vec![
293 "gitql".to_string(),
294 "--output".to_string(),
295 "text".to_string(),
296 ];
297 let command = parse_arguments(&arguments);
298 assert!(matches!(command, Command::Error { .. }));
299 }
300}