rox/
cli.rs

1use crate::models::{CiInfo, Docs, Pipeline, Task};
2use clap::{crate_version, Arg, ArgAction, Command};
3
4/// Dyanmically construct the CLI from the Roxfile
5pub fn construct_cli(
6    tasks: &[Task],
7    pipelines: &Option<Vec<Pipeline>>,
8    docs: &Option<Vec<Docs>>,
9    ci: &Option<CiInfo>,
10) -> clap::Command {
11    let mut cli = cli_builder(true);
12
13    // CI
14    if ci.is_some() {
15        // TODO: Add a flag to only show failures
16        cli = cli.subcommand(Command::new("ci").about("View CI pipeline information."));
17    }
18
19    // Docs
20    if let Some(docs) = docs {
21        let docs_subcommands = build_docs_subcommands(docs);
22        cli = cli.subcommands(vec![docs_subcommands]);
23    }
24
25    // Tasks
26    let task_subcommands = build_task_subcommands(tasks);
27    cli = cli.subcommands(vec![task_subcommands]);
28
29    // Pipelines
30    if let Some(pipelines) = pipelines {
31        let pipeline_subcommands = build_pipeline_subcommands(pipelines);
32        cli = cli.subcommands(vec![pipeline_subcommands]);
33    }
34    cli
35}
36
37/// Construct the CLI
38pub fn cli_builder(strict_subcommands: bool) -> Command {
39    Command::new("rox")
40        .about("Rox: The Robust Developer Experience CLI")
41        .next_display_order(None)
42        .version(crate_version!())
43        .arg_required_else_help(true)
44        .allow_external_subcommands(!strict_subcommands)
45        // TODO: Add a "watch" flag to run the command on file changes to a path?
46        .arg(
47            Arg::new("roxfile")
48                .long("file")
49                .short('f')
50                .default_value("roxfile.yml")
51                .help("Path to a Roxfile"),
52        )
53        .subcommand(
54            Command::new("logs")
55                .about("View logs for Rox invocations.")
56                .arg(
57                    Arg::new("number")
58                        .help("The number of logs to view.")
59                        .required(false)
60                        .value_parser(clap::value_parser!(i8))
61                        .default_value("1"),
62                ),
63        )
64}
65
66pub fn build_docs_subcommands(docs: &[Docs]) -> Command {
67    let subcommands: Vec<Command> = docs
68        .iter()
69        .map(|doc| Command::new(&doc.name).about(doc.description.clone().unwrap_or_default()))
70        .collect();
71
72    Command::new("docs")
73        .about("Display various kinds of documentation.")
74        .next_display_order(None)
75        .arg_required_else_help(true)
76        .subcommands(subcommands)
77}
78
79/// Build the `task` subcommand with individual tasks nested as subcommands
80pub fn build_task_subcommands(tasks: &[Task]) -> Command {
81    let subcommands: Vec<Command> = tasks
82        .iter()
83        .filter(|target| !target.hide.unwrap_or_default())
84        .map(|task| Command::new(&task.name).about(task.description.to_owned().unwrap_or_default()))
85        .collect();
86
87    Command::new("task")
88        .about("Discrete executable tasks.")
89        .long_about("Discrete units of execution containing a single runnable command.")
90        .arg_required_else_help(true)
91        .subcommands(subcommands)
92}
93
94/// Build the `pipelines` subcommand with individual pipelines as subcommands
95pub fn build_pipeline_subcommands(pipelines: &[Pipeline]) -> Command {
96    let subcommands: Vec<Command> = pipelines
97        .iter()
98        .map(|pipeline| {
99            Command::new(&pipeline.name).about(pipeline.description.clone().unwrap_or_default())
100        })
101        .collect();
102
103    Command::new("pl")
104        .about("Pipelines composed of multiple tasks.")
105        .next_display_order(None)
106        .long_about("Set(s) of task(s) composed into multiple stages.")
107        .arg_required_else_help(true)
108        .arg(
109            Arg::new("parallel")
110                .long("parallel")
111                .short('p')
112                .required(false)
113                .action(ArgAction::SetTrue)
114                .help("Run the pipeline's tasks in parallel."),
115        )
116        .subcommands(subcommands)
117}