gitignore_template_generator/parser/
api.rs1use clap::Parser;
2
3use crate::{
4 constant,
5 validator::{CliArgsValidator, DefaultCliArgsValidator},
6};
7
8use std::ffi::OsString;
9
10use crate::ProgramExit;
11pub use crate::parser::impls::DefaultArgsParser;
12
13#[derive(Parser, Debug, PartialEq, Default)]
19#[command(version, author, long_about = None)]
20#[command(about = constant::parser_infos::ABOUT)]
21#[command(help_template = "\
22{before-help}
23{usage-heading} {usage}
24
25{about-with-newline}
26{all-args}{after-help}
27
28Version: {version}
29Author: {author}
30")]
31#[command(disable_help_flag = true, disable_version_flag = true)]
32pub struct Args {
33 #[arg(
38 required_unless_present_any = vec!["author", "version", "help"],
39 value_parser = DefaultCliArgsValidator::has_no_commas,
40 help = constant::help_messages::TEMPLATE_NAMES
41 )]
42 pub template_names: Vec<String>,
43
44 #[arg(
51 short = constant::cli_options::SERVER_URL.short,
52 long = constant::cli_options::SERVER_URL.long,
53 help = constant::help_messages::SERVER_URL,
54 default_value = constant::template_generator::BASE_URL
55 )]
56 pub server_url: String,
57
58 #[arg(
65 short = constant::cli_options::ENDPOINT_URI.short,
66 long = constant::cli_options::ENDPOINT_URI.long,
67 help = constant::help_messages::ENDPOINT_URI,
68 default_value = constant::template_generator::URI
69 )]
70 pub endpoint_uri: String,
71
72 #[arg(
78 id = "help",
79 short = constant::cli_options::HELP.short,
80 long = constant::cli_options::HELP.long,
81 action = clap::ArgAction::SetTrue,
82 help = constant::help_messages::HELP
83 )]
84 pub show_help: bool,
85
86 #[arg(
92 id = "version",
93 short = constant::cli_options::VERSION.short,
94 long = constant::cli_options::VERSION.long,
95 action = clap::ArgAction::SetTrue,
96 help = constant::help_messages::VERSION
97 )]
98 pub show_version: bool,
99
100 #[arg(
106 id = "author",
107 short = constant::cli_options::AUTHOR.short,
108 long = constant::cli_options::AUTHOR.long,
109 action = clap::ArgAction::SetTrue,
110 help = constant::help_messages::AUTHOR
111 )]
112 pub show_author: bool,
113}
114
115impl Args {
116 pub fn with_template_names(mut self, template_names: Vec<String>) -> Self {
129 self.template_names = template_names;
130 self
131 }
132
133 pub fn with_server_url(mut self, server_url: &str) -> Self {
146 self.server_url = server_url.to_string();
147 self
148 }
149
150 pub fn with_endpoint_uri(mut self, endpoint_uri: &str) -> Self {
163 self.endpoint_uri = endpoint_uri.to_string();
164 self
165 }
166}
167
168pub trait ArgsParser {
170 fn parse(args: impl IntoIterator<Item = OsString>) -> Args;
185
186 fn try_parse(
204 args: impl IntoIterator<Item = OsString>,
205 ) -> Result<Args, ProgramExit>;
206}
207
208#[cfg(test)]
209mod tests {
210 use rstest::*;
211
212 use super::*;
213 use crate::helper::*;
214
215 mod default_args_parser {
216 use super::*;
217
218 mod try_parse {
219 use super::*;
220
221 mod success {
222 use crate::{ExitKind, constant};
223
224 use super::*;
225
226 #[rstest]
227 #[case("-V")]
228 #[case("--version")]
229 #[case("-V rust")]
230 #[case("rust -V")]
231 #[case("rust -s foo -V")]
232 #[case("rust -e bar -V")]
233 #[case("-aV")]
234 fn it_parses_version_cli_option(#[case] cli_args: &str) {
235 let cli_args = parse_cli_args(cli_args);
236 let parsed_args = DefaultArgsParser::try_parse(cli_args);
237
238 let actual_error = parsed_args.as_ref().err();
239 let expected_error = ProgramExit {
240 message: format!(
241 "{} {}",
242 env!("CARGO_PKG_NAME"),
243 env!("CARGO_PKG_VERSION")
244 ),
245 exit_status: 0,
246 styled_message: None,
247 kind: ExitKind::VersionInfos,
248 };
249 let expected_error = Some(&expected_error);
250
251 assert!(actual_error.is_some());
252 assert_eq!(actual_error, expected_error);
253 }
254
255 #[rstest]
256 #[case("-h")]
257 #[case("--help")]
258 #[case("-h rust")]
259 #[case("rust -h")]
260 #[case("rust -s foo -h")]
261 #[case("rust -e bar -h")]
262 #[case("-aVh")]
263 fn it_parses_help_cli_option(#[case] cli_args: &str) {
264 let cli_args = parse_cli_args(cli_args);
265 let parsed_args = DefaultArgsParser::try_parse(cli_args);
266
267 let actual_error = parsed_args.as_ref().err();
268 let expected_error = ProgramExit {
269 message: get_help_message(),
270 exit_status: 0,
271 styled_message: Some(get_ansi_help_message()),
272 kind: ExitKind::HelpInfos,
273 };
274 let expected_error = Some(&expected_error);
275
276 assert!(actual_error.is_some());
277 assert_eq!(actual_error, expected_error);
278 }
279
280 #[rstest]
281 #[case("-a")]
282 #[case("--author")]
283 #[case("-a rust")]
284 #[case("rust -a")]
285 #[case("rust -s foo -a")]
286 #[case("rust -e bar -a")]
287 fn it_parses_author_cli_option_preemptively(
288 #[case] cli_args: &str,
289 ) {
290 let cli_args = parse_cli_args(cli_args);
291 let parsed_args = DefaultArgsParser::try_parse(cli_args);
292
293 let actual_error = parsed_args.as_ref().err();
294 let expected_error = ProgramExit {
295 message: env!("CARGO_PKG_AUTHORS").to_string(),
296 exit_status: 0,
297 styled_message: None,
298 kind: ExitKind::AuthorInfos,
299 };
300 let expected_error = Some(&expected_error);
301
302 assert!(actual_error.is_some());
303 assert_eq!(actual_error, expected_error);
304 }
305
306 #[rstest]
307 #[case("rust")]
308 #[case("rust python node")]
309 fn it_parses_pos_args_without_server_url_cli_option(
310 #[case] cli_options: &str,
311 ) {
312 let cli_args = parse_cli_args(cli_options);
313 let parsed_args = DefaultArgsParser::try_parse(cli_args);
314
315 let actual_result = parsed_args.as_ref().ok();
316 let expected_result = Args::default()
317 .with_template_names(make_string_vec(cli_options))
318 .with_server_url(constant::template_generator::BASE_URL)
319 .with_endpoint_uri(constant::template_generator::URI);
320 let expected_result = Some(&expected_result);
321
322 assert!(actual_result.is_some());
323 assert_eq!(actual_result, expected_result);
324 }
325
326 #[rstest]
327 #[case("rust -s https://test.com")]
328 #[case("rust --server-url https://test.com")]
329 fn it_parses_pos_args_with_server_url_cli_option(
330 #[case] cli_args: &str,
331 ) {
332 let cli_args = parse_cli_args(cli_args);
333 let parsed_args = DefaultArgsParser::try_parse(cli_args);
334
335 let actual_result = parsed_args.as_ref().ok();
336 let expected_result = Args::default()
337 .with_template_names(make_string_vec("rust"))
338 .with_server_url("https://test.com")
339 .with_endpoint_uri(constant::template_generator::URI);
340 let expected_result = Some(&expected_result);
341
342 assert!(actual_result.is_some());
343 assert_eq!(actual_result, expected_result);
344 }
345
346 #[rstest]
347 #[case("rust -e /test/api")]
348 #[case("rust --endpoint-uri /test/api")]
349 fn it_parses_pos_args_with_endpoint_uri_cli_option(
350 #[case] cli_args: &str,
351 ) {
352 let cli_args = parse_cli_args(cli_args);
353 let parsed_args = DefaultArgsParser::try_parse(cli_args);
354
355 let actual_result = parsed_args.as_ref().ok();
356 let expected_result = Args::default()
357 .with_template_names(make_string_vec("rust"))
358 .with_server_url(constant::template_generator::BASE_URL)
359 .with_endpoint_uri("/test/api");
360 let expected_result = Some(&expected_result);
361
362 assert!(actual_result.is_some());
363 assert_eq!(actual_result, expected_result);
364 }
365 }
366
367 mod failure {
368 use crate::{ExitKind, constant};
369
370 use super::*;
371
372 #[test]
373 fn it_fails_parsing_when_no_pos_args_given() {
374 let cli_args = parse_cli_args("");
375 let parsed_args = DefaultArgsParser::try_parse(cli_args);
376
377 let actual_error = parsed_args.as_ref().err();
378 let expected_error = ProgramExit {
379 message: load_expectation_file_as_string(
380 "no_pos_args_error",
381 ),
382 exit_status: constant::exit_status::GENERIC,
383
384 styled_message: Some(load_expectation_file_as_string(
385 "ansi_no_pos_args_error",
386 )),
387 kind: ExitKind::Error,
388 };
389 let expected_error = Some(&expected_error);
390
391 assert!(actual_error.is_some());
392 assert_eq!(actual_error, expected_error);
393 }
394
395 #[test]
396 fn it_fails_parsing_when_commas_in_pos_args() {
397 let cli_args = parse_cli_args("python,java");
398 let parsed_args = DefaultArgsParser::try_parse(cli_args);
399
400 let actual_error = parsed_args.as_ref().err();
401 let expected_error = ProgramExit {
402 message: load_expectation_file_as_string(
403 "comma_pos_args_error",
404 ),
405 exit_status: constant::exit_status::GENERIC,
406
407 styled_message: Some(load_expectation_file_as_string(
408 "ansi_comma_pos_args_error",
409 )),
410 kind: ExitKind::Error,
411 };
412 let expected_error = Some(&expected_error);
413
414 assert!(actual_error.is_some());
415 assert_eq!(actual_error, expected_error);
416 }
417
418 #[test]
419 fn it_fails_parsing_when_server_url_but_no_pos_args() {
420 let cli_args = parse_cli_args("-s https://test.com");
421 let parsed_args = DefaultArgsParser::try_parse(cli_args);
422
423 let actual_error = parsed_args.as_ref().err();
424 let expected_error = ProgramExit {
425 message: load_expectation_file_as_string(
426 "server_url_no_pos_args_error",
427 ),
428 exit_status: constant::exit_status::GENERIC,
429
430 styled_message: Some(load_expectation_file_as_string(
431 "ansi_server_url_no_pos_args_error",
432 )),
433 kind: ExitKind::Error,
434 };
435 let expected_error = Some(&expected_error);
436
437 assert!(actual_error.is_some());
438 assert_eq!(actual_error, expected_error);
439 }
440
441 #[test]
442 fn it_fails_parsing_when_endpoint_uri_but_no_pos_args() {
443 let cli_args = parse_cli_args("-e /test/api");
444 let parsed_args = DefaultArgsParser::try_parse(cli_args);
445
446 let actual_error = parsed_args.as_ref().err();
447 let expected_error = ProgramExit {
448 message: load_expectation_file_as_string(
449 "endpoint_uri_no_pos_args_error",
450 ),
451 exit_status: constant::exit_status::GENERIC,
452
453 styled_message: Some(load_expectation_file_as_string(
454 "ansi_endpoint_uri_no_pos_args_error",
455 )),
456 kind: ExitKind::Error,
457 };
458 let expected_error = Some(&expected_error);
459
460 assert!(actual_error.is_some());
461 assert_eq!(actual_error, expected_error);
462 }
463
464 #[test]
465 fn it_fails_parsing_when_inexistent_cli_option() {
466 let cli_args = parse_cli_args("-x");
467 let parsed_args = DefaultArgsParser::try_parse(cli_args);
468
469 let actual_error = parsed_args.as_ref().err();
470 let expected_error = ProgramExit {
471 message: load_expectation_file_as_string(
472 "unexpected_argument_error",
473 ),
474 exit_status: constant::exit_status::GENERIC,
475 styled_message: Some(load_expectation_file_as_string(
476 "ansi_unexpected_argument_error",
477 )),
478 kind: ExitKind::Error,
479 };
480 let expected_error = Some(&expected_error);
481
482 assert!(actual_error.is_some());
483 assert_eq!(actual_error, expected_error);
484 }
485 }
486 }
487 }
488}