mps-rs 1.6.2

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

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)
    }
}