things3_cloud/commands/
project.rs1use crate::app::Cli;
2use crate::commands::Command;
3use crate::ui::render_element_to_string;
4use crate::ui::views::project::{ProjectHeadingGroup, ProjectView};
5use anyhow::Result;
6use clap::Args;
7use iocraft::prelude::*;
8use std::collections::BTreeMap;
9use std::sync::Arc;
10
11#[derive(Debug, Args)]
12#[command(about = "Show all tasks in a project")]
13pub struct ProjectArgs {
14 pub project_id: String,
16 #[arg(long)]
18 pub detailed: bool,
19}
20
21impl Command for ProjectArgs {
22 fn run_with_ctx(
23 &self,
24 cli: &Cli,
25 out: &mut dyn std::io::Write,
26 ctx: &mut dyn crate::cmd_ctx::CmdCtx,
27 ) -> Result<()> {
28 let store = Arc::new(cli.load_store()?);
29 let today = ctx.today();
30 let (task_opt, err, ambiguous) = store.resolve_mark_identifier(&self.project_id);
31 let Some(project) = task_opt else {
32 eprintln!("{err}");
33 for match_task in ambiguous {
34 eprintln!(" {}", match_task.title);
35 }
36 return Ok(());
37 };
38
39 if !project.is_project() {
40 eprintln!("Not a project: {}", project.title);
41 return Ok(());
42 }
43
44 let children = store
45 .tasks(None, Some(false), None)
46 .into_iter()
47 .filter(|t| store.effective_project_uuid(t).as_ref() == Some(&project.uuid))
48 .collect::<Vec<_>>();
49
50 let headings = store
51 .tasks_by_uuid
52 .values()
53 .filter(|t| t.is_heading() && !t.trashed && t.project.as_ref() == Some(&project.uuid))
54 .cloned()
55 .map(|h| (h.uuid.clone(), h))
56 .collect::<BTreeMap<_, _>>();
57
58 let mut ungrouped = Vec::new();
59 let mut by_heading: BTreeMap<_, Vec<_>> = BTreeMap::new();
60 for t in children.clone() {
61 if let Some(heading_uuid) = &t.action_group
62 && headings.contains_key(heading_uuid)
63 {
64 by_heading.entry(heading_uuid.clone()).or_default().push(t);
65 continue;
66 }
67 ungrouped.push(t);
68 }
69
70 let mut sorted_heading_uuids = by_heading.keys().cloned().collect::<Vec<_>>();
71 sorted_heading_uuids.sort_by_key(|u| headings.get(u).map(|h| h.index).unwrap_or(0));
72 ungrouped.sort_by_key(|t| t.index);
73 for items in by_heading.values_mut() {
74 items.sort_by_key(|t| t.index);
75 }
76
77 let heading_groups = sorted_heading_uuids
78 .iter()
79 .filter_map(|heading_uuid| {
80 let heading = headings.get(heading_uuid)?;
81 let tasks = by_heading.get(heading_uuid)?;
82 Some(ProjectHeadingGroup {
83 title: heading.title.clone(),
84 items: tasks.iter().collect::<Vec<_>>(),
85 })
86 })
87 .collect::<Vec<_>>();
88
89 let mut ui = element! {
90 ContextProvider(value: Context::owned(store.clone())) {
91 ContextProvider(value: Context::owned(today)) {
92 ProjectView(
93 project: &project,
94 ungrouped: ungrouped.iter().collect::<Vec<_>>(),
95 heading_groups,
96 detailed: self.detailed,
97 no_color: cli.no_color,
98 )
99 }
100 }
101 };
102 let rendered = render_element_to_string(&mut ui, cli.no_color);
103 writeln!(out, "{}", rendered)?;
104
105 Ok(())
106 }
107}