rox/
lib.rs

1mod cli;
2pub mod models;
3mod modules;
4mod utils;
5
6use crate::cli::{cli_builder, construct_cli};
7use crate::modules::execution::{execute_pipeline, execute_task};
8use crate::modules::{ci, docs, logs};
9use std::collections::HashMap;
10use std::error::Error;
11
12type RoxResult<T> = Result<T, Box<dyn Error>>;
13
14/// Get the filepath argument from the CLI
15///
16/// This is required because we might need to
17/// dynamically populate the CLI based on this arg
18fn get_filepath_arg_value() -> String {
19    let cli = cli_builder(false);
20    // Get the file arg from the CLI if set
21    let cli_matches = cli.clone().arg_required_else_help(false).get_matches();
22    cli_matches.get_one::<String>("roxfile").unwrap().to_owned()
23}
24
25/// Entrypoint for the Crate CLI
26pub async fn rox() -> RoxResult<()> {
27    let start = std::time::Instant::now();
28
29    // NOTE: Due to the dynamically generated nature of the CLI,
30    // It is required to parse the CLI matches twice. Once to get
31    // the filename arg and once to actually build the CLI.
32
33    // Get the file arg from the CLI if set
34    let file_path = get_filepath_arg_value();
35    let roxfile = models::RoxFile::build(&file_path)?;
36    utils::print_horizontal_rule();
37
38    // Build & Generate the CLI based on the loaded Roxfile
39    let cli = construct_cli(
40        &roxfile.tasks,
41        &roxfile.pipelines,
42        &roxfile.docs,
43        &roxfile.ci,
44    );
45    let cli_matches = cli.get_matches();
46
47    let task_map: HashMap<String, models::Task> = std::collections::HashMap::from_iter(
48        roxfile
49            .tasks
50            .into_iter()
51            .map(|task| (task.name.to_owned(), task)),
52    );
53
54    let (_, args) = cli_matches.subcommand().unwrap();
55    let subcommand_name = args.subcommand_name().unwrap_or("default");
56
57    // Execute the Command
58    match cli_matches.subcommand_name() {
59        Some("docs") => {
60            let documentation = roxfile
61                .docs
62                .iter()
63                .flatten()
64                .find(|doc| doc.name == subcommand_name)
65                .unwrap();
66            docs::display_docs(documentation);
67            std::process::exit(0);
68        }
69        Some("logs") => {
70            let number = args.get_one::<i8>("number").unwrap();
71            logs::display_logs(number);
72            std::process::exit(0);
73        }
74        Some("ci") => {
75            assert!(roxfile.ci.is_some());
76            ci::display_ci_status(roxfile.ci.unwrap()).await;
77            std::process::exit(0);
78        }
79        Some("pl") => {
80            let parallel = args.get_flag("parallel");
81            let pipeline = roxfile
82                .pipelines
83                .into_iter()
84                .flatten()
85                .find(|pipeline| pipeline.name == subcommand_name)
86                .unwrap(); // Clap will catch a non-existent Pipeline for us
87            execute_pipeline(pipeline, &task_map, parallel);
88        }
89        Some("task") => execute_task(task_map.get(subcommand_name).unwrap().to_owned()),
90        _ => unreachable!("Invalid subcommand"),
91    };
92
93    println!(
94        "> Total elapsed time: {}s | {}ms",
95        start.elapsed().as_secs(),
96        start.elapsed().as_millis(),
97    );
98
99    Ok(())
100}