use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use crate::typst::io::write_if_changed;
const RUNTIME_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/src/assets/typst-runtime");
const GENERATED_SYNTAX_THEME_FILE: &str = "runtime/00_syntax-theme.typ";
fn runtime_source_files() -> Result<Vec<PathBuf>> {
let mut files = Vec::new();
collect_runtime_files(Path::new(RUNTIME_DIR), &mut files)?;
files.sort_unstable_by_key(|path| {
path.strip_prefix(RUNTIME_DIR)
.unwrap_or(path)
.as_os_str()
.to_owned()
});
Ok(files)
}
fn collect_runtime_files(dir: &Path, files: &mut Vec<PathBuf>) -> Result<()> {
for entry in fs::read_dir(dir).with_context(|| format!("failed to read {}", dir.display()))? {
let entry = entry.with_context(|| format!("failed to read {}", dir.display()))?;
let path = entry.path();
let file_type = entry
.file_type()
.with_context(|| format!("failed to stat {}", path.display()))?;
if file_type.is_dir() {
collect_runtime_files(&path, files)?;
} else if path.extension() == Some(OsStr::new("typ")) {
files.push(path);
}
}
Ok(())
}
fn runtime_relative_path(path: &Path) -> PathBuf {
path.strip_prefix(RUNTIME_DIR)
.map(Path::to_path_buf)
.unwrap_or_else(|_| {
path.file_name()
.map(PathBuf::from)
.unwrap_or_else(|| path.to_path_buf())
})
}
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_path in runtime_source_files()? {
let rel = runtime_relative_path(&source_path);
let destination = runtime_dir.join(&rel);
let source = fs::read_to_string(&source_path)
.with_context(|| format!("failed to read {}", source_path.display()))?;
write_if_changed(&destination, source)?;
}
let facade = calepin_dir.join("calepin.typ");
write_if_changed(&facade, runtime_facade_source()?)?;
Ok(facade)
}
#[cfg(test)]
#[path = "runtime_tests.rs"]
mod runtime_tests;