calepin 0.0.14

A Rust CLI for preprocessing Typst documents with executable code chunks
use anyhow::{anyhow, Context, Result};
use serde_json::Value;

pub(super) fn parse_query_values(query_json: &str) -> Result<Vec<Value>> {
    serde_json::from_str(query_json).context("failed to parse typst query output")
}

pub(super) fn parse_metadata_values(query_json: &str) -> Result<Vec<Value>> {
    let root: Value = serde_json::from_str(query_json)?;
    let array = root
        .as_array()
        .ok_or_else(|| anyhow!("typst query output must be an array"))?;
    array
        .iter()
        .map(|item| {
            item.get("value")
                .cloned()
                .ok_or_else(|| anyhow!("metadata item is missing `value`"))
        })
        .collect()
}

pub(super) fn is_calepin_chunk_metadata(value: &Value) -> bool {
    value.get("func") == Some(&Value::String("metadata".into()))
        && value
            .get("label")
            .and_then(Value::as_str)
            .is_some_and(|value| value == "<calepin-chunk>")
}

pub(super) fn is_raw_code_block(value: &Value) -> bool {
    value.get("func") == Some(&Value::String("raw".into()))
        && value.get("block").and_then(Value::as_bool) == Some(true)
}

pub(super) fn is_calepin_fence_label_metadata(value: &Value) -> bool {
    value.get("func") == Some(&Value::String("metadata".into()))
        && value
            .get("label")
            .and_then(Value::as_str)
            .is_some_and(|value| value == "<calepin-fence-label>")
}

pub(super) fn value_for<'a>(object: &'a Value, key: &str) -> Option<&'a Value> {
    let value = object.get(key)?;
    if is_auto(value) || value.is_null() {
        None
    } else {
        Some(value)
    }
}

pub(super) fn is_auto(value: &Value) -> bool {
    value.as_str() == Some("auto")
}

pub(super) fn extract_text(value: &Value) -> Option<String> {
    if let Some(s) = value.as_str() {
        return Some(s.to_string());
    }
    if let Some(text) = value.get("text").and_then(Value::as_str) {
        return Some(text.to_string());
    }
    if let Some(children) = value.get("children").and_then(Value::as_array) {
        let mut text = String::new();
        for child in children {
            if let Some(child_text) = extract_text(child) {
                text.push_str(&child_text);
            }
        }
        return Some(text);
    }
    None
}