calepin 0.0.22

A Rust CLI for preprocessing Typst documents with executable code chunks
use std::fs;
use std::path::{Path, PathBuf};

use anyhow::{Context, Result};

use crate::typst::io::write_if_changed;

const GENERATED_SYNTAX_THEME_FILE: &str = "runtime/00_syntax-theme.typ";

struct RuntimeFile {
    path: &'static str,
    source: &'static str,
}

include!(concat!(env!("OUT_DIR"), "/typst_runtime_assets.rs"));

fn runtime_source_files() -> &'static [RuntimeFile] {
    RUNTIME_FILES
}

fn runtime_facade_source() -> Result<String> {
    Ok(r#"// Generated by Calepin. Do not edit.

#import "runtime/core/state.typ" as state
#import "runtime/core/target.typ" as target
#import "runtime/notebook/render.typ" as render
#import "runtime/notebook/options.typ" as options
#import "runtime/notebook/chunk.typ" as chunks
#import "runtime/elements/mod.typ" as elementmod

#let pages = state.pages
#let setup = options.setup
#let chunk = chunks.chunk
#let inline = chunks.inline
#let results = chunks.results
#let chunk_from_raw_plain = chunks.chunk_from_raw_plain
#let code-block = render.code-block
#let elements = elementmod

#let _mode = target._mode
#let _is-html = target._is-html
#let _is-paged = target._is-paged
#let _is-query = target._is-query
#let _is-render = target._is-render
#let _call-defaults = state._call-defaults
#let _disable-raw-chunk-transforms = state._disable-raw-chunk-transforms
#let _resolve-options = options._resolve-options
#let _html-themed-raw-block = render._html-themed-raw-block
#let _without-raw-chunk-transforms = chunks._without-raw-chunk-transforms
#let _fenced-chunks-runs = chunks._fenced-chunks-runs
"#
    .to_string())
}

#[cfg(test)]
pub fn write_runtime(root: &Path) -> Result<PathBuf> {
    write_runtime_with_syntax_theme(root, &crate::html::HtmlSyntaxTheme::builtin())
}

pub(crate) fn write_runtime_with_syntax_theme(
    root: &Path,
    syntax_theme: &crate::html::HtmlSyntaxTheme,
) -> Result<PathBuf> {
    let calepin_dir = root.join(".calepin");
    let runtime_dir = calepin_dir.join("runtime");
    fs::create_dir_all(&runtime_dir)
        .with_context(|| format!("failed to create {}", runtime_dir.display()))?;

    write_if_changed(
        &calepin_dir.join(GENERATED_SYNTAX_THEME_FILE),
        syntax_theme.typst_runtime_source(),
    )?;

    for source_file in runtime_source_files() {
        let destination = runtime_dir.join(source_file.path);
        write_if_changed(&destination, source_file.source)?;
    }

    let facade = calepin_dir.join("calepin.typ");
    write_if_changed(&facade, runtime_facade_source()?)?;
    Ok(facade)
}

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

    #[test]
    fn runtime_source_files_are_embedded_relative_typ_files() {
        let files = runtime_source_files();

        assert!(!files.is_empty(), "expected embedded Typst runtime files");
        for file in files {
            let path = Path::new(file.path);
            assert!(
                path.is_relative(),
                "runtime asset path should be relative: {}",
                file.path
            );
            assert_eq!(
                path.extension().and_then(|extension| extension.to_str()),
                Some("typ")
            );
            assert!(
                !file.source.is_empty(),
                "runtime asset should have embedded source: {}",
                file.path
            );
        }
    }
}

#[cfg(test)]
#[path = "runtime_tests.rs"]
mod runtime_tests;