gitql_cli/
arguments.rs

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