pub mod open;
pub mod list;
pub mod append;
pub mod edit;
pub mod delete;
pub mod search;
pub mod stats;
pub mod tags;
pub mod export;
pub mod git;
pub mod update;
pub mod config_cmd;
pub mod notify;
pub mod daemon;
pub mod meta_cmd;
use colored::Colorize;
use indexmap::IndexMap;
use crate::elements::{Element, ElementKind};
use crate::ref_resolver::RefResolver;
pub fn type_badge(kind: &ElementKind) -> String {
match kind {
ElementKind::Task => "[task]".green().bold().to_string(),
ElementKind::Note => "[note]".cyan().bold().to_string(),
ElementKind::Log => "[log]".yellow().bold().to_string(),
ElementKind::Reminder => "[reminder]".magenta().bold().to_string(),
ElementKind::MpsGroup => "[@mps]".white().to_string(),
ElementKind::Character => "[character]".blue().bold().to_string(),
ElementKind::Unknown => "[unknown]".white().to_string(),
}
}
pub fn element_extra(el: &Element) -> String {
match el {
Element::Task { data, .. } => {
let s = data.status_str();
let colored = if data.is_done() {
s.green().to_string()
} else {
s.yellow().to_string()
};
format!("({}) ", colored)
}
Element::Log { data, .. } => {
data.duration_str()
.map(|d| format!("({}) ", d.yellow()))
.unwrap_or_default()
}
Element::Reminder { data, .. } => {
data.at.as_deref()
.map(|t| format!("({}) ", t.magenta()))
.unwrap_or_default()
}
Element::Character { data, .. } => {
data.name.as_deref()
.map(|n| format!("({}) ", n.bold()))
.unwrap_or_default()
}
_ => String::new(),
}
}
pub fn tags_str(tags: &[String]) -> String {
if tags.is_empty() {
String::new()
} else {
format!(" {}", format!("[{}]", tags.join(", ")).white())
}
}
pub struct DisplayOpts {
pub type_filter: Option<String>,
pub tag_filter: Option<String>,
pub status_filter: Option<String>,
pub name_filter: Option<String>,
}
pub fn visible(el: &Element, opts: &DisplayOpts) -> bool {
if el.is_unknown() { return false; }
if let Some(ref tf) = opts.type_filter {
if el.sign() != tf { return false; }
}
if let Some(ref tag) = opts.tag_filter {
if !el.tags().iter().any(|t| t == tag) { return false; }
}
if let Some(ref sf) = opts.status_filter {
match el {
Element::Task { data, .. } => {
if data.status_str() != sf { return false; }
}
_ => return false,
}
}
if let Some(ref nf) = opts.name_filter {
match el {
Element::Character { data, .. } => {
if data.name.as_deref().map(|n| n.to_lowercase()) != Some(nf.to_lowercase()) {
return false;
}
}
_ => return false,
}
}
true
}
pub fn print_element(el: &Element, depth: usize, ref_str: Option<&str>) {
let indent = " ".repeat(depth + 1);
let badge = type_badge(&el.kind());
let extra = element_extra(el);
let body_line = el.body_str().trim().lines().next().unwrap_or("").trim();
let tags = tags_str(el.tags());
if let Some(r) = ref_str {
print!("{}{} ", indent, format!("{:<12}", r).white());
} else {
print!("{}", indent);
}
println!("{} {}{}{}", badge, extra, body_line, tags);
}
pub fn print_tree(
elements: &IndexMap<String, Element>,
opts: &DisplayOpts,
resolver: Option<&RefResolver>,
show_refs: bool,
) -> usize {
if elements.is_empty() { return 0; }
let mut sorted: Vec<(&String, &Element)> = elements.iter().collect();
sorted.sort_by(|(a, _), (b, _)| {
let a_parts: Vec<u64> = a.split('.').filter_map(|s| s.parse().ok()).collect();
let b_parts: Vec<u64> = b.split('.').filter_map(|s| s.parse().ok()).collect();
a_parts.cmp(&b_parts)
});
let root_depth = sorted.first().map(|(k, _)| k.split('.').count()).unwrap_or(1);
let mut shown = 0usize;
for (ref_key, el) in &sorted {
let depth = ref_key.split('.').count();
if depth <= root_depth { continue; }
let render_depth = depth - root_depth - 1;
let human_ref: String = resolver
.and_then(|r| r.to_human(ref_key))
.map(|s| s.to_string())
.unwrap_or_else(|| ref_key.to_string());
if el.is_mps_group() {
let prefix = format!("{}.", ref_key);
let any_visible = sorted.iter().any(|(k, v)| {
k.starts_with(&prefix) && !v.is_mps_group() && visible(v, opts)
});
if !any_visible { continue; }
let indent = " ".repeat(render_depth + 1);
if show_refs {
print!("{}{} ", indent, format!("{:<12}", &human_ref).white());
} else {
print!("{}", indent);
}
println!("{}", "[@mps]".white());
} else {
if !visible(el, opts) { continue; }
let ref_str = if show_refs { Some(human_ref.as_str()) } else { None };
print_element(el, render_depth, ref_str);
shown += 1;
}
}
shown
}