mps-rs 1.6.1

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

#[derive(Serialize)]
struct ExportRecord {
    date:   String,
    #[serde(rename = "ref")]
    ref_key: String,
    #[serde(rename = "type")]
    kind:   String,
    tags:   String,
    body:   String,
    #[serde(skip_serializing_if = "Option::is_none")]
    status: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    at:     Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    start:  Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    end:    Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    name:   Option<String>,
}

pub fn run(
    config:      &Config,
    dates:       Vec<NaiveDate>,
    format:      &str,
    type_filter: Option<String>,
) -> Result<()> {
    if !["json", "csv"].contains(&format) {
        bail!("Unknown format '{}'. Use: json, csv", format);
    }

    let store = Store::new(&config.storage_dir);
    let mut records: Vec<ExportRecord> = Vec::new();

    for d in &dates {
        let date_str = d.format("%Y-%m-%d").to_string();
        let elements = store.parse_date(*d)?;

        for (ref_key, el) in &elements {
            if el.is_mps_group() || el.is_unknown() { continue; }
            if let Some(ref tf) = type_filter {
                if el.sign() != tf { continue; }
            }

            let rec = build_record(&date_str, ref_key, el);
            records.push(rec);
        }
    }

    if format == "json" {
        println!("{}", serde_json::to_string_pretty(&records)?);
    } else {
        // CSV
        let mut wtr = csv::Writer::from_writer(std::io::stdout());
        wtr.write_record(["date", "ref", "type", "tags", "body", "status", "at", "start", "end", "name"])?;
        for r in &records {
            wtr.write_record([
                &r.date,
                &r.ref_key,
                &r.kind,
                &r.tags,
                &r.body,
                r.status.as_deref().unwrap_or(""),
                r.at.as_deref().unwrap_or(""),
                r.start.as_deref().unwrap_or(""),
                r.end.as_deref().unwrap_or(""),
                r.name.as_deref().unwrap_or(""),
            ])?;
        }
        wtr.flush()?;
    }

    Ok(())
}

fn build_record(date_str: &str, ref_key: &str, el: &Element) -> ExportRecord {
    let tags = el.tags().join(",");
    let body = el.body_str().trim().to_string();

    match el {
        Element::Task { data, .. } => ExportRecord {
            date: date_str.into(),
            ref_key: ref_key.into(),
            kind: "task".into(),
            tags,
            body,
            status: Some(data.status_str().into()),
            at: None, start: None, end: None, name: None,
        },
        Element::Note { .. } => ExportRecord {
            date: date_str.into(), ref_key: ref_key.into(), kind: "note".into(),
            tags, body, status: None, at: None, start: None, end: None, name: None,
        },
        Element::Log { data, .. } => ExportRecord {
            date: date_str.into(), ref_key: ref_key.into(), kind: "log".into(),
            tags, body, status: None, at: None,
            start: data.start.clone(),
            end:   data.end.clone(),
            name: None,
        },
        Element::Reminder { data, .. } => ExportRecord {
            date: date_str.into(), ref_key: ref_key.into(), kind: "reminder".into(),
            tags, body, status: None, at: data.at.clone(), start: None, end: None, name: None,
        },
        Element::Character { data, .. } => ExportRecord {
            date: date_str.into(), ref_key: ref_key.into(), kind: "character".into(),
            tags, body, status: None, at: None, start: None, end: None,
            name: data.name.clone(),
        },
        _ => ExportRecord {
            date: date_str.into(), ref_key: ref_key.into(),
            kind: el.sign().into(),
            tags, body, status: None, at: None, start: None, end: None, name: None,
        },
    }
}