calepin 0.0.13

A Rust CLI for preprocessing Typst documents with executable code chunks
mod eval;
mod query;

use anyhow::Result;
use serde_json::Value;
use std::collections::HashMap;
use std::path::{Path, PathBuf};

use crate::typst::model::LayoutPaths;

const PAGE_META_LABEL: &str = "website-metadata";
const PAGE_SYNC_SELECTOR: &str = "<calepin-page>";

#[derive(Debug, Clone)]
pub struct PreprocessMetadata {
    pub setup_json: String,
    pub page_meta: Option<Value>,
    pub chunks_json: String,
}

pub fn preprocess_metadata(
    typst: &Path,
    layout: &LayoutPaths,
    input: &Path,
    results_input: &str,
) -> Result<PreprocessMetadata> {
    if eval::is_available(typst) {
        eval::preprocess_metadata(typst, layout, input, results_input)
    } else {
        query::preprocess_metadata(typst, layout, input, results_input)
    }
}

pub fn page_anchors(typst: &Path, layout: &LayoutPaths) -> Result<HashMap<String, usize>> {
    if eval::is_available(typst) {
        eval::page_anchors(typst, layout)
    } else {
        query::page_anchors(typst, layout)
    }
}

fn results_input(layout: &LayoutPaths) -> String {
    crate::typst::paths::artifact_reference(&layout.root, &layout.results_path)
}

fn parse_page_anchor_entries(root: &Value) -> Result<HashMap<String, usize>> {
    let array = root
        .as_array()
        .ok_or_else(|| anyhow::anyhow!("typst page sync output must be an array"))?;
    let mut pages = HashMap::new();

    for item in array {
        let Some(label) = item.get("label").and_then(Value::as_str) else {
            continue;
        };
        let Some(page) = item
            .get("page")
            .and_then(Value::as_u64)
            .and_then(|page| usize::try_from(page).ok())
        else {
            continue;
        };
        pages.entry(label.to_string()).or_insert(page);
    }

    Ok(pages)
}

fn root_relative(path: &Path, root: &Path) -> PathBuf {
    path.strip_prefix(root).unwrap_or(path).to_path_buf()
}

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

    #[test]
    fn parses_page_anchor_entries() {
        let pages = parse_page_anchor_entries(&serde_json::json!([
            {"label": "chunk-1", "page": 3}
        ]))
        .unwrap();

        assert_eq!(pages.get("chunk-1"), Some(&3));
    }
}