From abe5140de0e7fdb534caecd93da3a6ee56f4bf64 Mon Sep 17 00:00:00 2001 From: Andrew Cassidy Date: Mon, 24 Apr 2023 22:34:00 -0700 Subject: [PATCH] Initial skeleton copying typst-cli --- .gitignore | 4 -- Cargo.toml | 9 +++ src/main.rs | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 3ca43ae..698065d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,6 @@ debug/ target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.toml b/Cargo.toml index 214dd23..79270ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +comemo = "0.2.2" +elsa = "1.8.1" +html = "0.5.2" +notify = "5.1.0" +same-file = "1.0.6" +siphasher = "0.3.10" +typst = { git = "https://github.com/typst/typst.git" } +typst-library = { git = "https://github.com/typst/typst.git" } + diff --git a/src/main.rs b/src/main.rs index e7a11a9..e16fa88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,180 @@ +#![feature(hashmap_internals)] + +use std::cell::{OnceCell, RefCell, RefMut}; +use std::collections::HashMap; +use std::fs::{self}; +use std::hash::Hash; +use std::path::{Path, PathBuf}; + +use comemo::Prehashed; +use elsa::FrozenVec; +use same_file::Handle; +use siphasher::sip128::{Hasher128, SipHasher13}; +use typst::diag::{FileError, FileResult}; +use typst::eval::Library; +use typst::font::{Font, FontBook}; +use typst::syntax::{Source, SourceId}; +use typst::util::{Buffer, PathExt}; +use typst::World; + + +/// Holds canonical data for all paths pointing to the same entity. +#[derive(Default)] +struct PathSlot { + source: OnceCell>, + buffer: OnceCell>, +} + +/// A hash that is the same for all paths pointing to the same entity. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +struct PathHash(u128); + +impl PathHash { + fn new(path: &Path) -> FileResult { + let f = |e| FileError::from_io(e, path); + let handle = Handle::from_path(path).map_err(f)?; + let mut state = SipHasher13::new(); + handle.hash(&mut state); + Ok(Self(state.finish128().as_u128())) + } +} + +/// Read a file. +fn read(path: &Path) -> FileResult> { + let f = |e| FileError::from_io(e, path); + if fs::metadata(path).map_err(f)?.is_dir() { + Err(FileError::IsDirectory) + } else { + fs::read(path).map_err(f) + } +} + +pub struct WebWorld { + root: PathBuf, + library: Prehashed, + book: Prehashed, + // unused + hashes: RefCell>>, + paths: RefCell>, + sources: FrozenVec>, + main: SourceId, +} + +impl WebWorld { + fn new(root: PathBuf) -> Self { + WebWorld { + root, + library: Prehashed::new(typst_library::build()), + book: Prehashed::new(Default::default()), + hashes: RefCell::default(), + paths: RefCell::default(), + sources: FrozenVec::new(), + main: SourceId::detached(), + } + } + + fn slot(&self, path: &Path) -> FileResult> { + let mut hashes = self.hashes.borrow_mut(); + let hash = match hashes.get(path).cloned() { + Some(hash) => hash, + None => { + let hash = PathHash::new(path); + if let Ok(canon) = path.canonicalize() { + hashes.insert(canon.normalize(), hash.clone()); + } + hashes.insert(path.into(), hash.clone()); + hash + } + }?; + + Ok(RefMut::map(self.paths.borrow_mut(), |paths| { + paths.entry(hash).or_default() + })) + } + + /// Add a source file to the world and return its ID + fn insert(&self, path: &Path, text: String) -> SourceId { + let id = SourceId::from_u16(self.sources.len() as u16); + let source = Source::new(id, path, text); + self.sources.push(Box::new(source)); + id + } + + fn relevant(&mut self, event: ¬ify::Event) -> bool { + match &event.kind { + notify::EventKind::Any => {} + notify::EventKind::Access(_) => return false, + notify::EventKind::Create(_) => return true, + notify::EventKind::Modify(kind) => match kind { + notify::event::ModifyKind::Any => {} + notify::event::ModifyKind::Data(_) => {} + notify::event::ModifyKind::Metadata(_) => return false, + notify::event::ModifyKind::Name(_) => return true, + notify::event::ModifyKind::Other => return false, + }, + notify::EventKind::Remove(_) => {} + notify::EventKind::Other => return false, + } + + event.paths.iter().any(|path| self.dependant(path)) + } + + fn dependant(&self, path: &Path) -> bool { + self.hashes.borrow().contains_key(&path.normalize()) + || PathHash::new(path) + .map_or(false, |hash| self.paths.borrow().contains_key(&hash)) + } + + fn reset(&mut self) { + self.sources.as_mut().clear(); + self.hashes.borrow_mut().clear(); + self.paths.borrow_mut().clear(); + } +} + + +impl World for WebWorld { + fn root(&self) -> &Path { + &self.root + } + + fn library(&self) -> &Prehashed { + &self.library + } + + fn main(&self) -> &Source { + self.source(self.main) + } + + fn resolve(&self, path: &Path) -> FileResult { + self.slot(path)? + .source + .get_or_init(|| { + let buf = read(path)?; + let text = String::from_utf8(buf)?; + Ok(self.insert(path, text)) + }) + .clone() + } + + fn source(&self, id: SourceId) -> &Source { + &self.sources[id.into_u16() as usize] + } + + fn book(&self) -> &Prehashed { + &self.book // always empty + } + + fn font(&self, _: usize) -> Option { None } + + fn file(&self, path: &Path) -> FileResult { + self.slot(path)? + .buffer + .get_or_init(|| read(path).map(Buffer::from)) + .clone() + } +} + fn main() { println!("Hello, world!"); }