nomograph-workflow 0.1.0

Shared workflow layer for nomograph claim-substrate tools: Store adapter, phase state machine, v1 legacy detection, cross-tool helpers.
Documentation
//! Cross-tool helpers.
//!
//! Previously duplicated in synthesist/store.rs and lattice/store.rs;
//! consolidated here so a fix (or the addition of a new helper) lands
//! in one place.

use anyhow::{Context, Result};
use serde_json::Value;

/// Split a `tree/spec` identifier into its two parts. Prescriptive
/// error on malformed input so CLI users see the expected shape.
pub fn parse_tree_spec(input: &str) -> Result<(String, String)> {
    let (tree, spec) = input
        .split_once('/')
        .context("identifier must be <tree>/<spec>, e.g. keaton/graphs")?;
    if tree.is_empty() || spec.is_empty() {
        anyhow::bail!("identifier must be <tree>/<spec>, e.g. keaton/graphs");
    }
    Ok((tree.to_string(), spec.to_string()))
}

/// Today's date as `YYYY-MM-DD` in local time. Used by commands that
/// default a `date` / `event_date` prop to "today".
pub fn today() -> String {
    use time::macros::format_description;
    let fmt = format_description!("[year]-[month]-[day]");
    time::OffsetDateTime::now_local()
        .unwrap_or_else(|_| time::OffsetDateTime::now_utc())
        .format(&fmt)
        .unwrap_or_else(|_| "1970-01-01".into())
}

/// Render a JSON value as a single line on stdout. Every synthesist
/// and lattice command emits JSON by convention; routing through this
/// helper keeps the output shape consistent.
pub fn json_out(v: &Value) -> Result<()> {
    println!("{}", serde_json::to_string(v).context("serialize output")?);
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_tree_spec_ok() {
        let (t, s) = parse_tree_spec("keaton/graphs").unwrap();
        assert_eq!(t, "keaton");
        assert_eq!(s, "graphs");
    }

    #[test]
    fn parse_tree_spec_errors_on_missing_slash() {
        assert!(parse_tree_spec("keaton").is_err());
    }

    #[test]
    fn parse_tree_spec_errors_on_empty_halves() {
        assert!(parse_tree_spec("/graphs").is_err());
        assert!(parse_tree_spec("keaton/").is_err());
    }

    #[test]
    fn today_shape_is_yyyy_mm_dd() {
        let s = today();
        assert_eq!(s.len(), 10);
        assert_eq!(s.chars().nth(4), Some('-'));
        assert_eq!(s.chars().nth(7), Some('-'));
    }
}