1#[cfg(test)]
7#[path = "cli_parser_test.rs"]
8mod cli_parser_test;
9
10use crate::completion::generate_completions;
11
12use crate::cli::{
13 AUTHOR, DEFAULT_LOG_LEVEL, DEFAULT_OUTPUT_FORMAT, DEFAULT_TASK_NAME, DESCRIPTION, VERSION,
14};
15use crate::profile;
16use crate::types::{CliArgs, GlobalConfig};
17use cliparser::types::{
18 Argument, ArgumentHelp, ArgumentOccurrence, ArgumentValueType, CliParsed, CliSpec,
19 CliSpecMetaInfo, PositionalArgument,
20};
21
22use crate::error::CargoMakeError;
23
24fn get_args(
25 cli_parsed: &CliParsed,
26 global_config: &GlobalConfig,
27 command_name: &str,
28 sub_command: bool,
29) -> CliArgs {
30 let mut cli_args = CliArgs::new();
31
32 cli_args.command = if sub_command {
33 let mut binary = "cargo ".to_string();
34 binary.push_str(command_name);
35 binary
36 } else {
37 command_name.to_string()
38 };
39
40 cli_args.env = to_owned_vec(cli_parsed.argument_values.get("env"));
41
42 cli_args.build_file = match cli_parsed.get_first_value("makefile") {
43 Some(value) => Some(value),
44 None => None,
45 };
46
47 cli_args.completion = match cli_parsed.get_first_value("completion") {
48 Some(value) => Some(value.to_string()),
49 None => None,
50 };
51
52 cli_args.cwd = cli_parsed.get_first_value("cwd");
53
54 let default_log_level = match global_config.log_level {
55 Some(ref value) => value.to_string(),
56 None => DEFAULT_LOG_LEVEL.to_string(),
57 };
58 cli_args.log_level = if cli_parsed.arguments.contains("verbose") {
59 "verbose".to_string()
60 } else if cli_parsed.arguments.contains("quiet") {
61 "error".to_string()
62 } else if cli_parsed.arguments.contains("silent") {
63 "off".to_string()
64 } else {
65 cli_parsed
66 .get_first_value("loglevel")
67 .unwrap_or(default_log_level)
68 .to_string()
69 };
70
71 let default_disable_color = match global_config.disable_color {
72 Some(value) => value,
73 None => false,
74 };
75 cli_args.disable_color = cli_parsed.arguments.contains("no-color")
76 || envmnt::is("CARGO_MAKE_DISABLE_COLOR")
77 || default_disable_color;
78
79 cli_args.print_time_summary = cli_parsed.arguments.contains("time-summary")
80 || envmnt::is("CARGO_MAKE_PRINT_TIME_SUMMARY");
81
82 cli_args.env_file = match cli_parsed.get_first_value("envfile") {
83 Some(value) => Some(value.to_string()),
84 None => None,
85 };
86
87 cli_args.output_format = cli_parsed
88 .get_first_value("output-format")
89 .unwrap_or(DEFAULT_OUTPUT_FORMAT.to_string())
90 .to_string();
91
92 cli_args.list_category_steps = match cli_parsed.get_first_value("list-category-steps") {
93 Some(value) => Some(value.to_string()),
94 None => None,
95 };
96
97 cli_args.output_file = match cli_parsed.get_first_value("output-file") {
98 Some(value) => Some(value.to_string()),
99 None => None,
100 };
101
102 let profile_name = cli_parsed
103 .get_first_value("profile")
104 .unwrap_or_else(profile::default_profile);
105 cli_args.profile = Some(profile_name.to_string());
106
107 cli_args.disable_check_for_updates = cli_parsed.arguments.contains("disable-check-for-updates");
108 cli_args.experimental = cli_parsed.arguments.contains("experimental");
109 cli_args.print_only = cli_parsed.arguments.contains("print-steps");
110 cli_args.disable_workspace = cli_parsed.arguments.contains("no-workspace");
111 cli_args.disable_on_error = cli_parsed.arguments.contains("no-on-error");
112 cli_args.allow_private = cli_parsed.arguments.contains("allow-private");
113 cli_args.skip_init_end_tasks = cli_parsed.arguments.contains("skip-init-end-tasks");
114 cli_args.list_all_steps = cli_parsed.arguments.contains("list-steps");
115 cli_args.diff_execution_plan = cli_parsed.arguments.contains("diff-steps");
116 cli_args.hide_uninteresting = cli_parsed.arguments.contains("hide-uninteresting");
117
118 cli_args.skip_tasks_pattern = match cli_parsed.get_first_value("skip-tasks-pattern") {
119 Some(value) => Some(value.to_string()),
120 None => None,
121 };
122
123 let default_task_name = match global_config.default_task_name {
124 Some(ref value) => value.to_string(),
125 None => DEFAULT_TASK_NAME.to_string(),
126 };
127 let task = cli_parsed
128 .get_first_value("task")
129 .unwrap_or(default_task_name);
130 let task_cmd = to_owned_vec(cli_parsed.argument_values.get("TASK_CMD")).unwrap_or(vec![]);
131 let task_cmd_slice = task_cmd.as_slice();
132 let (task, arguments) = match task_cmd_slice {
133 &[] => (task, None),
134 &[ref task_name, ref task_args @ ..] => {
135 let args_strings = task_args.iter().map(|item| item.to_string()).collect();
136 (task_name.to_string(), Some(args_strings))
137 }
138 };
139 cli_args.task = task;
140 cli_args.arguments = arguments;
141
142 cli_args
143}
144
145pub fn create_cli(global_config: &GlobalConfig, mut spec: CliSpec, default_meta: bool) -> CliSpec {
146 let default_task_name = match global_config.default_task_name {
147 Some(ref value) => value.as_str(),
148 None => &DEFAULT_TASK_NAME,
149 };
150 let default_log_level = match global_config.log_level {
151 Some(ref value) => value.as_str(),
152 None => &DEFAULT_LOG_LEVEL,
153 };
154
155 if default_meta {
156 spec = spec
157 .set_meta_info(Some(CliSpecMetaInfo {
158 author: Some(AUTHOR.to_string()),
159 version: Some(VERSION.to_string()),
160 description: Some(DESCRIPTION.to_string()),
161 project: Some("cargo-make".to_string()),
162 help_post_text: Some(
163 "See more info at: https://github.com/sagiegurari/cargo-make".to_string(),
164 ),
165 }))
166 .add_command("makers")
167 .add_subcommand(vec!["cargo", "make"])
168 .add_subcommand(vec!["cargo-make", "make"]); }
170 add_arguments(spec, default_task_name, default_log_level)
171}
172
173fn add_arguments(spec: CliSpec, default_task_name: &str, default_log_level: &str) -> CliSpec {
174 spec
175 .add_argument(Argument {
176 name: "help".to_string(),
177 key: vec!["--help".to_string(), "-h".to_string()],
178 argument_occurrence: ArgumentOccurrence::Single,
179 value_type: ArgumentValueType::None,
180 default_value: None,
181 help: Some(ArgumentHelp::Text("Print help information".to_string())),
182 })
183 .add_argument(Argument {
184 name: "version".to_string(),
185 key: vec!["--version".to_string(), "-V".to_string()],
186 argument_occurrence: ArgumentOccurrence::Single,
187 value_type: ArgumentValueType::None,
188 default_value: None,
189 help: Some(ArgumentHelp::Text("Print version information".to_string())),
190 })
191 .add_argument(Argument {
192 name: "makefile".to_string(),
193 key: vec!["--makefile".to_string()],
194 argument_occurrence: ArgumentOccurrence::Single,
195 value_type: ArgumentValueType::Single,
196 default_value: None,
197 help: Some(ArgumentHelp::TextAndParam(
198 "The optional toml file containing the tasks definitions".to_string(),
199 "FILE".to_string(),
200 )),
201 })
202 .add_argument(Argument {
203 name: "task".to_string(),
204 key: vec!["--task".to_string(), "-t".to_string()],
205 argument_occurrence: ArgumentOccurrence::Single,
206 value_type: ArgumentValueType::Single,
207 default_value: Some(default_task_name.to_string()),
208 help: Some(ArgumentHelp::TextAndParam(
209 "The task name to execute (can omit the flag if the task name is the last argument)".to_string(),
210 "TASK".to_string(),
211 )),
212 })
213 .add_argument(Argument {
214 name: "profile".to_string(),
215 key: vec!["--profile".to_string(), "-p".to_string()],
216 argument_occurrence: ArgumentOccurrence::Single,
217 value_type: ArgumentValueType::Single,
218 default_value: Some(profile::default_profile()),
219 help: Some(ArgumentHelp::TextAndParam(
220 "The profile name (will be converted to lower case)".to_string(),
221 "PROFILE".to_string(),
222 )),
223 })
224 .add_argument(Argument {
225 name: "completion".to_string(),
226 key: vec!["--completion".to_string()],
227 argument_occurrence: ArgumentOccurrence::Single,
228 value_type: ArgumentValueType::Single,
229 default_value: None,
230 help: Some(ArgumentHelp::TextAndParam(
231 "Will enable completion for the defined tasks for a given shell".to_string(),
232 "COMPLETION".to_string(),
233 )),
234 })
235 .add_argument(Argument {
236 name: "cwd".to_string(),
237 key: vec!["--cwd".to_string()],
238 argument_occurrence: ArgumentOccurrence::Single,
239 value_type: ArgumentValueType::Single,
240 default_value: None,
241 help: Some(ArgumentHelp::TextAndParam(
242 "Will set the current working directory. The search for the makefile will be from this directory if defined.".to_string(),
243 "DIRECTORY".to_string(),
244 )),
245 })
246 .add_argument(Argument {
247 name: "no-workspace".to_string(),
248 key: vec!["--no-workspace".to_string()],
249 argument_occurrence: ArgumentOccurrence::Single,
250 value_type: ArgumentValueType::None,
251 default_value: None,
252 help: Some(ArgumentHelp::Text(
253 "Disable workspace support (tasks are triggered on workspace and not on members)".to_string(),
254 )),
255 })
256 .add_argument(Argument {
257 name: "no-on-error".to_string(),
258 key: vec!["--no-on-error".to_string()],
259 argument_occurrence: ArgumentOccurrence::Single,
260 value_type: ArgumentValueType::None,
261 default_value: None,
262 help: Some(ArgumentHelp::Text(
263 "Disable on error flow even if defined in config sections".to_string(),
264 )),
265 })
266 .add_argument(Argument {
267 name: "allow-private".to_string(),
268 key: vec!["--allow-private".to_string()],
269 argument_occurrence: ArgumentOccurrence::Single,
270 value_type: ArgumentValueType::None,
271 default_value: None,
272 help: Some(ArgumentHelp::Text(
273 "Allow invocation of private tasks".to_string(),
274 )),
275 })
276 .add_argument(Argument {
277 name: "skip-init-end-tasks".to_string(),
278 key: vec!["--skip-init-end-tasks".to_string()],
279 argument_occurrence: ArgumentOccurrence::Single,
280 value_type: ArgumentValueType::None,
281 default_value: None,
282 help: Some(ArgumentHelp::Text(
283 "If set, init and end tasks are skipped".to_string(),
284 )),
285 })
286 .add_argument(Argument {
287 name: "skip-tasks-pattern".to_string(),
288 key: vec!["--skip-tasks".to_string()],
289 argument_occurrence: ArgumentOccurrence::Single,
290 value_type: ArgumentValueType::Single,
291 default_value: None,
292 help: Some(ArgumentHelp::TextAndParam(
293 "Skip all tasks that match the provided regex (example: pre.*|post.*)".to_string(),
294 "SKIP_TASK_PATTERNS".to_string(),
295 )),
296 })
297 .add_argument(Argument {
298 name: "envfile".to_string(),
299 key: vec!["--env-file".to_string()],
300 argument_occurrence: ArgumentOccurrence::Single,
301 value_type: ArgumentValueType::Single,
302 default_value: None,
303 help: Some(ArgumentHelp::TextAndParam(
304 "Set environment variables from provided file".to_string(),
305 "FILE".to_string(),
306 )),
307 })
308 .add_argument(Argument {
309 name: "env".to_string(),
310 key: vec!["--env".to_string(), "-e".to_string()],
311 argument_occurrence: ArgumentOccurrence::Multiple,
312 value_type: ArgumentValueType::Single,
313 default_value: None,
314 help: Some(ArgumentHelp::TextAndParam(
315 "Set environment variables".to_string(),
316 "ENV".to_string(),
317 )),
318 })
319 .add_argument(Argument {
320 name: "loglevel".to_string(),
321 key: vec!["--loglevel".to_string(), "-l".to_string()],
322 argument_occurrence: ArgumentOccurrence::Single,
323 value_type: ArgumentValueType::Single,
324 default_value: Some(default_log_level.to_string()),
325 help: Some(ArgumentHelp::TextAndParam(
326 "The log level (verbose, info, error, off)".to_string(),
327 "LOG LEVEL".to_string(),
328 )),
329 })
330 .add_argument(Argument {
331 name: "verbose".to_string(),
332 key: vec!["--verbose".to_string(), "-v".to_string()],
333 argument_occurrence: ArgumentOccurrence::Single,
334 value_type: ArgumentValueType::None,
335 default_value: None,
336 help: Some(ArgumentHelp::Text(
337 "Sets the log level to verbose (shorthand for --loglevel verbose)".to_string(),
338 )),
339 })
340 .add_argument(Argument {
341 name: "quiet".to_string(),
342 key: vec!["--quiet".to_string()],
343 argument_occurrence: ArgumentOccurrence::Single,
344 value_type: ArgumentValueType::None,
345 default_value: None,
346 help: Some(ArgumentHelp::Text(
347 "Sets the log level to error (shorthand for --loglevel error)".to_string(),
348 )),
349 })
350 .add_argument(Argument {
351 name: "silent".to_string(),
352 key: vec!["--silent".to_string()],
353 argument_occurrence: ArgumentOccurrence::Single,
354 value_type: ArgumentValueType::None,
355 default_value: None,
356 help: Some(ArgumentHelp::Text(
357 "Sets the log level to off (shorthand for --loglevel off)".to_string(),
358 )),
359 })
360 .add_argument(Argument {
361 name: "no-color".to_string(),
362 key: vec!["--no-color".to_string()],
363 argument_occurrence: ArgumentOccurrence::Single,
364 value_type: ArgumentValueType::None,
365 default_value: None,
366 help: Some(ArgumentHelp::Text(
367 "Disables colorful output".to_string(),
368 )),
369 })
370 .add_argument(Argument {
371 name: "time-summary".to_string(),
372 key: vec!["--time-summary".to_string()],
373 argument_occurrence: ArgumentOccurrence::Single,
374 value_type: ArgumentValueType::None,
375 default_value: None,
376 help: Some(ArgumentHelp::Text(
377 "Print task level time summary at end of flow".to_string(),
378 )),
379 })
380 .add_argument(Argument {
381 name: "experimental".to_string(),
382 key: vec!["--experimental".to_string()],
383 argument_occurrence: ArgumentOccurrence::Single,
384 value_type: ArgumentValueType::None,
385 default_value: None,
386 help: Some(ArgumentHelp::Text(
387 "Allows access unsupported experimental predefined tasks.".to_string(),
388 )),
389 })
390 .add_argument(Argument {
391 name: "disable-check-for-updates".to_string(),
392 key: vec!["--disable-check-for-updates".to_string()],
393 argument_occurrence: ArgumentOccurrence::Single,
394 value_type: ArgumentValueType::None,
395 default_value: None,
396 help: Some(ArgumentHelp::Text(
397 "Disables the update check during startup".to_string(),
398 )),
399 })
400 .add_argument(Argument {
401 name: "output-format".to_string(),
402 key: vec!["--output-format".to_string()],
403 argument_occurrence: ArgumentOccurrence::Single,
404 value_type: ArgumentValueType::Single,
405 default_value: None,
406 help: Some(ArgumentHelp::TextAndParam(
407 "The print/list steps format (some operations do not support all formats) (default, short-description, markdown, markdown-single-page, markdown-sub-section, autocomplete)".to_string(),
408 "OUTPUT FORMAT".to_string(),
409 )),
410 })
411 .add_argument(Argument {
412 name: "output-file".to_string(),
413 key: vec!["--output-file".to_string()],
414 argument_occurrence: ArgumentOccurrence::Single,
415 value_type: ArgumentValueType::Single,
416 default_value: None,
417 help: Some(ArgumentHelp::TextAndParam(
418 "The list steps output file name".to_string(),
419 "OUTPUT_FILE".to_string(),
420 )),
421 })
422 .add_argument(Argument {
423 name: "hide-uninteresting".to_string(),
424 key: vec!["--hide-uninteresting".to_string()],
425 argument_occurrence: ArgumentOccurrence::Single,
426 value_type: ArgumentValueType::None,
427 default_value: None,
428 help: Some(ArgumentHelp::Text(
429 "Hide any minor tasks such as pre/post hooks.".to_string(),
430 )),
431 })
432 .add_argument(Argument {
433 name: "print-steps".to_string(),
434 key: vec!["--print-steps".to_string()],
435 argument_occurrence: ArgumentOccurrence::Single,
436 value_type: ArgumentValueType::None,
437 default_value: None,
438 help: Some(ArgumentHelp::Text(
439 "Only prints the steps of the build in the order they will be invoked but without invoking them".to_string(),
440 )),
441 })
442 .add_argument(Argument {
443 name: "list-steps".to_string(),
444 key: vec!["--list-all-steps".to_string()],
445 argument_occurrence: ArgumentOccurrence::Single,
446 value_type: ArgumentValueType::None,
447 default_value: None,
448 help: Some(ArgumentHelp::Text(
449 "Lists all known steps".to_string(),
450 )),
451 })
452 .add_argument(Argument {
453 name: "list-category-steps".to_string(),
454 key: vec!["--list-category-steps".to_string()],
455 argument_occurrence: ArgumentOccurrence::Single,
456 value_type: ArgumentValueType::Single,
457 default_value: None,
458 help: Some(ArgumentHelp::TextAndParam(
459 "List steps for a given category".to_string(),
460 "CATEGORY".to_string(),
461 )),
462 })
463 .add_argument(Argument {
464 name: "diff-steps".to_string(),
465 key: vec!["--diff-steps".to_string()],
466 argument_occurrence: ArgumentOccurrence::Single,
467 value_type: ArgumentValueType::None,
468 default_value: None,
469 help: Some(ArgumentHelp::Text(
470 "Runs diff between custom flow and prebuilt flow (requires git)".to_string(),
471 )),
472 })
473 .set_positional_argument(Some(PositionalArgument {
474 name: "TASK_CMD".to_string(),
475 help: Some(ArgumentHelp::Text(
476 "The task to execute, potentially including arguments which can be accessed in the task itself.".to_string(),
477 )),
478 }))
479}
480
481pub fn parse_args(
482 global_config: &GlobalConfig,
483 command_name: &str,
484 sub_command: bool,
485 args: Option<Vec<&str>>,
486 spec: CliSpec,
487) -> Result<CliArgs, CargoMakeError> {
488 let cli_parsed = match args {
489 Some(args_vec) => cliparser::parse(&args_vec, &spec),
490 None => cliparser::parse_process(&spec),
491 }?;
492
493 if cli_parsed.arguments.contains("help") {
494 let help_text = cliparser::help(&spec);
496 println!("{}", help_text);
497 Err(CargoMakeError::ExitCode(std::process::ExitCode::SUCCESS))
498 } else if cli_parsed.arguments.contains("version") {
499 let version_text = cliparser::version(&spec);
501 println!("{}", version_text);
502 Err(CargoMakeError::ExitCode(std::process::ExitCode::SUCCESS))
503 } else if let Some(shell) = cli_parsed.get_first_value("completion") {
504 if let Err(e) = generate_completions(&shell) {
506 error!("Error generating completions: {}", e);
507 return Err(CargoMakeError::ExitCode(std::process::ExitCode::FAILURE));
508 }
509 Err(crate::error::CargoMakeError::ExitCode(
510 std::process::ExitCode::SUCCESS,
511 ))
512 } else {
513 Ok(get_args(
514 &cli_parsed,
515 &global_config,
516 command_name,
517 sub_command,
518 ))
519 }
520}
521
522pub fn parse(
523 global_config: &GlobalConfig,
524 command_name: &str,
525 sub_command: bool,
526) -> Result<CliArgs, CargoMakeError> {
527 parse_args(
528 global_config,
529 command_name,
530 sub_command,
531 None,
532 create_cli(&global_config, CliSpec::new(), true),
533 )
534}
535
536fn to_owned_vec(vec_option: Option<&Vec<String>>) -> Option<Vec<String>> {
537 match vec_option {
538 Some(vec) => Some(vec.to_owned()),
539 None => None,
540 }
541}