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}