1pub mod open;
2pub mod list;
3pub mod append;
4pub mod search;
5pub mod stats;
6pub mod tags;
7pub mod export;
8pub mod git;
9pub mod update;
10pub mod config_cmd;
11
12use colored::Colorize;
13use indexmap::IndexMap;
14use crate::elements::{Element, ElementKind};
15use crate::ref_resolver::RefResolver;
16
17pub fn type_badge(kind: &ElementKind) -> String {
20 match kind {
21 ElementKind::Task => "[task]".green().bold().to_string(),
22 ElementKind::Note => "[note]".cyan().bold().to_string(),
23 ElementKind::Log => "[log]".yellow().bold().to_string(),
24 ElementKind::Reminder => "[reminder]".magenta().bold().to_string(),
25 ElementKind::MpsGroup => "[@mps]".white().to_string(),
26 ElementKind::Character => "[character]".blue().bold().to_string(),
27 ElementKind::Unknown => "[unknown]".white().to_string(),
28 }
29}
30
31pub fn element_extra(el: &Element) -> String {
33 match el {
34 Element::Task { data, .. } => {
35 let s = data.status_str();
36 let colored = if data.is_done() {
37 s.green().to_string()
38 } else {
39 s.yellow().to_string()
40 };
41 format!("({}) ", colored)
42 }
43 Element::Log { data, .. } => {
44 data.duration_str()
45 .map(|d| format!("({}) ", d.yellow()))
46 .unwrap_or_default()
47 }
48 Element::Reminder { data, .. } => {
49 data.at.as_deref()
50 .map(|t| format!("({}) ", t.magenta()))
51 .unwrap_or_default()
52 }
53 Element::Character { data, .. } => {
54 data.name.as_deref()
55 .map(|n| format!("({}) ", n.bold()))
56 .unwrap_or_default()
57 }
58 _ => String::new(),
59 }
60}
61
62pub fn tags_str(tags: &[String]) -> String {
63 if tags.is_empty() {
64 String::new()
65 } else {
66 format!(" {}", format!("[{}]", tags.join(", ")).white())
67 }
68}
69
70pub struct DisplayOpts {
72 pub type_filter: Option<String>,
73 pub tag_filter: Option<String>,
74 pub status_filter: Option<String>,
75 pub name_filter: Option<String>,
76}
77
78pub fn visible(el: &Element, opts: &DisplayOpts) -> bool {
79 if el.is_unknown() { return false; }
80 if let Some(ref tf) = opts.type_filter {
81 if el.sign() != tf { return false; }
82 }
83 if let Some(ref tag) = opts.tag_filter {
84 if !el.tags().iter().any(|t| t == tag) { return false; }
85 }
86 if let Some(ref sf) = opts.status_filter {
87 match el {
89 Element::Task { data, .. } => {
90 if data.status_str() != sf { return false; }
91 }
92 _ => return false,
93 }
94 }
95 if let Some(ref nf) = opts.name_filter {
96 match el {
98 Element::Character { data, .. } => {
99 if data.name.as_deref().map(|n| n.to_lowercase()) != Some(nf.to_lowercase()) {
100 return false;
101 }
102 }
103 _ => return false,
104 }
105 }
106 true
107}
108
109pub fn print_element(el: &Element, depth: usize, ref_str: Option<&str>) {
111 let indent = " ".repeat(depth + 1);
112 let badge = type_badge(&el.kind());
113 let extra = element_extra(el);
114 let body_line = el.body_str().trim().lines().next().unwrap_or("").trim();
115 let tags = tags_str(el.tags());
116
117 if let Some(r) = ref_str {
118 print!("{}{} ", indent, format!("{:<12}", r).white());
119 } else {
120 print!("{}", indent);
121 }
122 println!("{} {}{}{}", badge, extra, body_line, tags);
123}
124
125pub fn print_tree(
133 elements: &IndexMap<String, Element>,
134 opts: &DisplayOpts,
135 resolver: Option<&RefResolver>,
136 show_refs: bool,
137) -> usize {
138 if elements.is_empty() { return 0; }
139
140 let mut sorted: Vec<(&String, &Element)> = elements.iter().collect();
141 sorted.sort_by(|(a, _), (b, _)| {
142 let a_parts: Vec<u64> = a.split('.').filter_map(|s| s.parse().ok()).collect();
143 let b_parts: Vec<u64> = b.split('.').filter_map(|s| s.parse().ok()).collect();
144 a_parts.cmp(&b_parts)
145 });
146
147 let root_depth = sorted.first().map(|(k, _)| k.split('.').count()).unwrap_or(1);
148 let mut shown = 0usize;
149
150 for (ref_key, el) in &sorted {
151 let depth = ref_key.split('.').count();
152 if depth <= root_depth { continue; }
153 let render_depth = depth - root_depth - 1;
154
155 let human_ref: String = resolver
157 .and_then(|r| r.to_human(ref_key))
158 .map(|s| s.to_string())
159 .unwrap_or_else(|| ref_key.to_string());
160
161 if el.is_mps_group() {
162 let prefix = format!("{}.", ref_key);
163 let any_visible = sorted.iter().any(|(k, v)| {
164 k.starts_with(&prefix) && !v.is_mps_group() && visible(v, opts)
165 });
166 if !any_visible { continue; }
167
168 let indent = " ".repeat(render_depth + 1);
169 if show_refs {
170 print!("{}{} ", indent, format!("{:<12}", &human_ref).white());
171 } else {
172 print!("{}", indent);
173 }
174 println!("{}", "[@mps]".white());
175 } else {
176 if !visible(el, opts) { continue; }
177 let ref_str = if show_refs { Some(human_ref.as_str()) } else { None };
178 print_element(el, render_depth, ref_str);
179 shown += 1;
180 }
181 }
182
183 shown
184}