Skip to main content

sofie_cli/
tasks_command.rs

1use sofie_model::naming::task_name;
2use sofie_model::{App, Module, Task};
3
4use crate::routes_command::text_table;
5use crate::sorted;
6
7const HEADERS: [&str; 4] = ["NAME", "ARGUMENTS", "FUNCTION", "DESCRIPTION"];
8
9#[must_use]
10pub fn task_table(app: &App) -> String {
11    let mut rows: Vec<[String; 4]> = sorted::modules(app)
12        .into_iter()
13        .flat_map(|module| module.tasks.iter().map(move |task| row(module, task)))
14        .collect();
15    rows.sort();
16    text_table(HEADERS, &rows)
17}
18
19fn row(module: &Module, task: &Task) -> [String; 4] {
20    let function = module.code.function(&task.function);
21    [
22        task_name(module, &task.function),
23        task_arguments(module, task),
24        format!("{}::{}", module.krate.crate_name, task.function.join("::")),
25        function
26            .and_then(|function| function.doc.clone())
27            .unwrap_or_default(),
28    ]
29}
30
31/// As the binary reads them: `--field value`, an optional one in brackets.
32pub(crate) fn task_arguments(module: &Module, task: &Task) -> String {
33    let Some(input) = module
34        .code
35        .function(&task.function)
36        .and_then(|function| module.code.input_of(function))
37    else {
38        return String::new();
39    };
40    let fields: Vec<String> = input
41        .fields
42        .iter()
43        .map(|field| match field.ty.arguments.as_slice() {
44            [inner] if field.ty.name == "Option" => format!("[--{} {inner}]", field.name),
45            _ => format!("--{} {}", field.name, field.ty),
46        })
47        .collect();
48    fields.join(" ")
49}
50
51#[cfg(test)]
52mod tests {
53    use std::path::PathBuf;
54
55    use sofie_model::testing::{app, field, function, module, type_name};
56    use sofie_model::{InputDeclaration, Task};
57
58    use super::task_table;
59
60    #[test]
61    fn lists_each_task_with_the_arguments_its_input_reads() {
62        let mut order = module("order", "shop/modules/order");
63        let file = "shop/modules/order/src/tasks.rs";
64        let mut backfill = function("tasks::backfill", file, 3);
65        backfill.arguments = vec![type_name("Read"), type_name("Since")];
66        backfill.doc = Some("Fills totals of orders placed before totals were kept.".to_owned());
67        order.tasks = vec![Task {
68            file: PathBuf::from(file),
69            line: 3,
70            function: backfill.path.clone(),
71        }];
72        order.code.functions = vec![backfill];
73        order.code.inputs = vec![InputDeclaration {
74            name: "Since".to_owned(),
75            path: vec!["tasks".to_owned(), "Since".to_owned()],
76            from: None,
77            fields: vec![field("since", "Date", 1), field("limit", "Option<u32>", 1)],
78            unconstrained: Vec::new(),
79            file: PathBuf::from(file),
80            line: 1,
81        }];
82
83        let table = task_table(&app("shop", vec![order]));
84
85        let row: Vec<&str> = table
86            .lines()
87            .nth(1)
88            .unwrap()
89            .split("  ")
90            .filter(|cell| !cell.is_empty())
91            .map(str::trim)
92            .collect();
93        assert_eq!(
94            row,
95            [
96                "order.backfill",
97                "--since Date [--limit u32]",
98                "order::tasks::backfill",
99                "Fills totals of orders placed before totals were kept."
100            ],
101            "{table}"
102        );
103    }
104}