mps-rs 1.6.0

MPS — plain-text personal productivity CLI (Rust)
Documentation
use anyhow::{bail, Result};
use colored::Colorize;
use std::path::Path;
use crate::config::Config;
use crate::meta::{MetaLocal, MetaShared};

pub fn run(config: &Config, subcommand: Option<&str>) -> Result<()> {
    match subcommand.unwrap_or("show").to_lowercase().as_str() {
        "show"  => show(&config.storage_dir),
        "clear" => clear(&config.storage_dir),
        "edit"  => edit(&config.storage_dir),
        other   => bail!("Unknown meta subcommand '{}'. Use: show, clear, edit", other),
    }
}

fn show(storage_dir: &Path) -> Result<()> {
    let shared = MetaShared::load(storage_dir);
    let local  = MetaLocal::load(storage_dir);

    println!("{}", "── .mps.meta (git-tracked config layer) ─────────────".white());
    println!("  path    : {}", MetaShared::path(storage_dir).display());
    println!("  version : {}", shared.version);

    if !shared.config.type_aliases.is_empty() {
        let mut pairs: Vec<_> = shared.config.type_aliases.iter()
            .map(|(k, v)| format!("{}{}", k, v)).collect();
        pairs.sort();
        println!("  type_aliases    : {}", pairs.join(", "));
    }
    if !shared.config.command_aliases.is_empty() {
        let mut pairs: Vec<_> = shared.config.command_aliases.iter()
            .map(|(k, v)| format!("{}{}", k, v)).collect();
        pairs.sort();
        println!("  command_aliases : {}", pairs.join(", "));
    }
    if let Some(ref dc) = shared.config.default_command {
        println!("  default_command : {}", dc);
    }
    if !shared.config.custom_tags.is_empty() {
        println!("  custom_tags     : {}", shared.config.custom_tags.join(", "));
    }
    let n = &shared.config.notify;
    println!("  notify.enabled          : {}", n.enabled);
    println!("  notify.window_minutes   : {}", n.window_minutes);
    println!("  notify.notify_open_tasks: {}", n.notify_open_tasks);
    if let Some(ref t) = n.task_notify_at {
        println!("  notify.task_notify_at   : {}", t);
    }
    if !n.open_task_tags.is_empty() {
        println!("  notify.open_task_tags   : {}", n.open_task_tags.join(", "));
    }
    println!("  notify.overdue_days     : {}", n.overdue_days);

    println!();
    println!("{}", "── .mps.local (gitignored local state) ───────────────".white());
    println!("  path             : {}", MetaLocal::path(storage_dir).display());
    println!("  notified entries : {}", local.notified.len());
    if let Some(ref d) = local.last_task_date {
        println!("  last_task_date   : {}", d);
    }
    if !local.cache.tag_counts.is_empty() {
        println!("  cached tag counts: {} tags", local.cache.tag_counts.len());
    }

    Ok(())
}

fn clear(storage_dir: &Path) -> Result<()> {
    let path = MetaLocal::path(storage_dir);
    if path.exists() {
        std::fs::remove_file(&path)?;
        println!("  {} {}", "cleared".green(), path.display());
    } else {
        println!("  (nothing to clear — .mps.local does not exist)");
    }
    Ok(())
}

fn edit(storage_dir: &Path) -> Result<()> {
    let path = MetaShared::path(storage_dir);
    // Ensure the file exists so the editor doesn't open to a blank state.
    if !path.exists() {
        let empty = MetaShared::default();
        empty.save(storage_dir)?;
    }
    let editor = std::env::var("EDITOR")
        .or_else(|_| std::env::var("VISUAL"))
        .unwrap_or_else(|_| "vim".to_string());
    println!("{}", format!("Opening {} in editor", path.display()).white());
    std::process::Command::new(&editor)
        .arg(&path)
        .status()
        .map_err(|e| anyhow::anyhow!("failed to launch editor '{}': {}", editor, e))?;
    Ok(())
}