gitql_cli/
arguments.rs

1use crate::printer::OutputFormatKind;
2
3/// Arguments for GitQL
4#[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
14/// Create a new instance of Arguments with the default settings
15impl 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    // Add the current directory if no repository is passed
162    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}