1#[derive(Debug, PartialEq)]
2
3pub enum OutputFormat {
5 Render,
7 JSON,
9 CSV,
11}
12
13#[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
24impl 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 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}