Initial skeleton copying typst-cli

main
Andrew Cassidy 1 year ago
parent 0c2a5f6482
commit abe5140de0

4
.gitignore vendored

@ -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

@ -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" }

@ -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<FileResult<SourceId>>,
buffer: OnceCell<FileResult<Buffer>>,
}
/// 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<Self> {
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<Vec<u8>> {
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<Library>,
book: Prehashed<FontBook>,
// unused
hashes: RefCell<HashMap<PathBuf, FileResult<PathHash>>>,
paths: RefCell<HashMap<PathHash, PathSlot>>,
sources: FrozenVec<Box<Source>>,
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<RefMut<PathSlot>> {
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: &notify::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<Library> {
&self.library
}
fn main(&self) -> &Source {
self.source(self.main)
}
fn resolve(&self, path: &Path) -> FileResult<SourceId> {
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<FontBook> {
&self.book // always empty
}
fn font(&self, _: usize) -> Option<Font> { None }
fn file(&self, path: &Path) -> FileResult<Buffer> {
self.slot(path)?
.buffer
.get_or_init(|| read(path).map(Buffer::from))
.clone()
}
}
fn main() {
println!("Hello, world!");
}

Loading…
Cancel
Save