Skip to main content

termesh_tasks/
cargo.rs

1use termesh_core::TaskSpec;
2use termesh_filesystem::FileSystemService;
3use termesh_workspace::{ProjectKind, WorkspaceRoot};
4
5use crate::{
6    java::java_tasks, node::node_tasks, python::python_tasks, CargoOutputDecoder,
7    TaskOutputDecoder, TaskService, TextProblemDecoder,
8};
9
10const TASKS: &[(&str, &str, &str)] = &[
11    ("cargo.check", "Check", "check"),
12    ("cargo.build", "Build", "build"),
13    ("cargo.test", "Test", "test"),
14    ("cargo.clippy", "Clippy", "clippy"),
15];
16
17#[derive(Debug, Clone, Copy, Default)]
18pub struct AdapterTaskService;
19
20impl AdapterTaskService {
21    pub fn cargo_only() -> Self {
22        Self
23    }
24}
25
26impl TaskService for AdapterTaskService {
27    fn catalog(&self, root: &WorkspaceRoot, fs: &dyn FileSystemService) -> Vec<TaskSpec> {
28        let mut tasks = Vec::new();
29        if root.kinds.contains(&ProjectKind::Rust) {
30            tasks.extend(TASKS.iter().map(|(id, label, subcommand)| TaskSpec {
31                id: (*id).into(),
32                label: (*label).into(),
33                program: "cargo".into(),
34                args: vec![
35                    (*subcommand).into(),
36                    "--message-format=json-diagnostic-rendered-ansi".into(),
37                ],
38                cwd: root.path.clone(),
39            }));
40        }
41        if root.kinds.contains(&ProjectKind::Node) {
42            tasks.extend(node_tasks(fs, &root.path));
43        }
44        if root.kinds.contains(&ProjectKind::Python) {
45            tasks.extend(python_tasks(&root.path));
46        }
47        if root.kinds.contains(&ProjectKind::Java) {
48            tasks.extend(java_tasks(fs, &root.path));
49        }
50        tasks
51    }
52
53    fn decoder(&self, task: &TaskSpec) -> Option<Box<dyn TaskOutputDecoder>> {
54        if TASKS.iter().any(|(id, _, _)| *id == task.id) {
55            Some(Box::new(CargoOutputDecoder::new(task.cwd.clone())))
56        } else {
57            Some(Box::new(TextProblemDecoder::new(task.cwd.clone())))
58        }
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use std::path::Path;
66    use termesh_filesystem::RealFileSystem;
67
68    fn root(kind: ProjectKind) -> WorkspaceRoot {
69        WorkspaceRoot { path: "/p".into(), kind, kinds: vec![kind], detected: true }
70    }
71
72    #[test]
73    fn the_cargo_adapter_ignores_the_filesystem_it_is_handed() {
74        let service = AdapterTaskService::cargo_only();
75        let tasks = service.catalog(&root(ProjectKind::Rust), &RealFileSystem);
76        assert_eq!(tasks.len(), 4);
77    }
78
79    #[test]
80    fn rust_projects_get_exactly_four_curated_tasks() {
81        let service = AdapterTaskService::cargo_only();
82        let tasks = service.catalog(&root(ProjectKind::Rust), &RealFileSystem);
83        assert_eq!(
84            tasks.iter().map(|task| task.id.as_str()).collect::<Vec<_>>(),
85            ["cargo.check", "cargo.build", "cargo.test", "cargo.clippy"]
86        );
87        assert!(tasks.iter().all(|task| {
88            task.program == "cargo"
89                && task.args.last().map(String::as_str)
90                    == Some("--message-format=json-diagnostic-rendered-ansi")
91                && task.cwd == Path::new("/p")
92        }));
93    }
94
95    #[test]
96    fn non_rust_projects_get_no_cargo_tasks() {
97        let service = AdapterTaskService::cargo_only();
98        assert!(service.catalog(&root(ProjectKind::Node), &RealFileSystem).is_empty());
99    }
100
101    #[test]
102    fn a_python_project_offers_a_conventional_test_task() {
103        let service = AdapterTaskService::cargo_only();
104        let tasks = service.catalog(&root(ProjectKind::Python), &RealFileSystem);
105        assert!(tasks.iter().any(|task| task.program == "pytest"));
106    }
107}