1use std::collections::HashMap;
2
3use crate::api::rest::{Gateway, Project, Task};
4use color_eyre::{Result, eyre::eyre};
5
6#[derive(clap::Parser, Debug)]
7pub struct Params {
8 #[arg(short = 'f', long = "filter")]
10 pub filter: Option<String>,
11}
12
13pub async fn list(params: Params, gw: &Gateway) -> Result<()> {
15 let projects = gw.projects().await?;
16 if let Some(filter) = params.filter {
17 let tasks = gw.tasks(Some(&filter)).await?;
18 if tasks.is_empty() {
19 return Err(eyre!("no tasks match the given filter"))?;
20 }
21 let projects = filtered_projects(&projects, &tasks)?;
22 for (project, tasks) in projects.iter() {
23 println!("{} (Tasks: {})", &project, tasks);
24 }
25 return Ok(());
26 }
27 for project in projects.iter() {
28 println!("{}", &project);
29 }
30 Ok(())
31}
32
33fn filtered_projects<'a>(
34 projects: &'a [Project],
35 tasks: &'_ [Task],
36) -> Result<Vec<(&'a Project, usize)>> {
37 let hm = tasks.iter().fold(HashMap::<_, usize>::new(), |mut hm, t| {
38 *hm.entry(&t.project_id).or_default() += 1;
39 hm
40 });
41 let projects = projects
42 .iter()
43 .filter_map(|p| hm.get(&p.id).map(|tasks| (p, *tasks)))
44 .collect();
45 Ok(projects)
46}
47
48#[cfg(test)]
49mod test {
50 use super::*;
51 use crate::api::rest::{Project, Task};
52
53 #[tokio::test]
54 async fn filter_projects() {
55 let ps = vec![
56 Project::new("1", "one"),
57 Project::new("2", "two"),
58 Project::new("3", "three"),
59 ];
60 let ts = vec![
61 create_task("1", "1", "one"),
62 create_task("2", "1", "two"),
63 create_task("3", "2", "three"),
64 ];
65 let projects = filtered_projects(&ps, &ts).unwrap();
66 assert_eq!(projects.len(), 2);
67 assert_eq!(projects[0].0.id, "1");
68 assert_eq!(projects[0].1, 2);
69 assert_eq!(projects[1].0.id, "2");
70 assert_eq!(projects[1].1, 1);
71 }
72
73 fn create_task(id: &str, project_id: &str, content: &str) -> Task {
74 let mut task = crate::api::rest::Task::new(id, content);
75 task.project_id = project_id.to_string();
76 task
77 }
78}