mps-rs 1.6.1

MPS — plain-text personal productivity CLI (Rust)
Documentation
use anyhow::Result;
use chrono::NaiveDate;
use colored::Colorize;
use crate::config::Config;
use crate::elements::{Element, ElementKind};
use crate::store::Store;

pub fn run(
    config: &Config,
    dates:  Vec<NaiveDate>,
    all:    bool,
) -> Result<()> {
    let store = Store::new(&config.storage_dir);

    // Expand to all archive dates when --all is set
    let effective_dates: Vec<NaiveDate> = if all {
        store.all_file_dates()?
    } else {
        dates
    };

    let multi = effective_dates.len() > 1;

    let mut grand_tasks      = 0usize;
    let mut grand_notes      = 0usize;
    let mut grand_reminders  = 0usize;
    let mut grand_logs       = 0usize;
    let mut grand_log_mins   = 0i64;
    let mut grand_characters = 0usize;
    let mut any = false;

    for d in &effective_dates {
        let elements: Vec<Element> = store.parse_date(*d)?
            .into_values()
            .filter(|e| !e.is_mps_group() && !e.is_unknown())
            .collect();

        if elements.is_empty() { continue; }
        any = true;

        let tasks:      Vec<&Element> = elements.iter().filter(|e| e.kind() == ElementKind::Task).collect();
        let notes:      usize         = elements.iter().filter(|e| e.kind() == ElementKind::Note).count();
        let reminders:  usize         = elements.iter().filter(|e| e.kind() == ElementKind::Reminder).count();
        let logs:       Vec<&Element> = elements.iter().filter(|e| e.kind() == ElementKind::Log).collect();
        let characters: usize         = elements.iter().filter(|e| e.kind() == ElementKind::Character).count();

        let open_n = tasks.iter().filter(|e| {
            if let Element::Task { data, .. } = e { data.is_open() } else { false }
        }).count();
        let done_n = tasks.len() - open_n;

        let log_mins: i64 = logs.iter().map(|e| {
            if let Element::Log { data, .. } = e { data.duration_minutes().unwrap_or(0) } else { 0 }
        }).sum();

        grand_tasks      += tasks.len();
        grand_notes      += notes;
        grand_reminders  += reminders;
        grand_logs       += logs.len();
        grand_log_mins   += log_mins;
        grand_characters += characters;

        let mut parts: Vec<String> = Vec::new();

        if !tasks.is_empty() {
            let n = tasks.len();
            parts.push(format!(
                "{} task{} ({}, {})",
                n,
                if n != 1 { "s" } else { "" },
                format!("{} open", open_n).yellow(),
                format!("{} done", done_n).green()
            ));
        }
        if notes > 0 {
            parts.push(format!("{} note{}", notes, if notes != 1 { "s" } else { "" }));
        }
        if reminders > 0 {
            parts.push(format!("{} reminder{}", reminders, if reminders != 1 { "s" } else { "" }));
        }
        if !logs.is_empty() {
            let n = logs.len();
            let dur = format_duration(log_mins);
            let dur_suffix = if dur.is_empty() { String::new() } else { format!(" ({})", dur) };
            parts.push(format!("{} log{}{}", n, if n != 1 { "s" } else { "" }, dur_suffix));
        }
        if characters > 0 {
            parts.push(format!("{} character{}", characters, if characters != 1 { "s" } else { "" }));
        }

        println!("{}{}", d.format("%Y-%m-%d").to_string().white(), parts.join(", "));
    }

    if !any {
        println!("{}", "(no data found)".yellow());
        return Ok(());
    }

    if multi && any {
        println!("{}", "".repeat(44).white());
        let mut tparts: Vec<String> = Vec::new();
        if grand_tasks > 0      { tparts.push(format!("{} tasks", grand_tasks)); }
        if grand_notes > 0      { tparts.push(format!("{} notes", grand_notes)); }
        if grand_reminders > 0  { tparts.push(format!("{} reminders", grand_reminders)); }
        if grand_logs > 0 {
            let dur = format_duration(grand_log_mins);
            let dur_suffix = if dur.is_empty() { String::new() } else { format!(" ({} total)", dur) };
            tparts.push(format!("{} logs{}", grand_logs, dur_suffix));
        }
        if grand_characters > 0 { tparts.push(format!("{} characters", grand_characters)); }
        println!("Total: {}", tparts.join(", "));
    }

    Ok(())
}

fn format_duration(mins: i64) -> String {
    if mins <= 0 { return String::new(); }
    let h = mins / 60;
    let m = mins % 60;
    if m > 0 { format!("{}h{}m", h, m) } else { format!("{}h", h) }
}