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);
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))?;
match std::fs::read_to_string(&path).ok().and_then(|s| serde_json::from_str::<MetaShared>(&s).ok()) {
Some(_) => println!(" {} .mps.meta is valid JSON", "ok:".green()),
None => eprintln!(" {} .mps.meta contains invalid JSON — it will be ignored until fixed", "warn:".yellow()),
}
println!(" {} run {} to sync this config to other devices",
"hint:".cyan(), "mps autogit".bold());
Ok(())
}