Refactor path handling

This commit is contained in:
Laurenz 2021-06-01 12:46:01 +02:00
parent 9bdb0bdeff
commit 7218892c72
17 changed files with 147 additions and 121 deletions

View File

@ -6,7 +6,7 @@ edition = "2018"
[features] [features]
default = ["cli", "fs"] default = ["cli", "fs"]
cli = ["anyhow", "fs"] cli = ["anyhow", "fs", "same-file"]
fs = ["dirs", "memmap2", "same-file", "walkdir"] fs = ["dirs", "memmap2", "same-file", "walkdir"]
[workspace] [workspace]

View File

@ -41,16 +41,16 @@ fn benchmarks(c: &mut Criterion) {
// Prepare intermediate results, run warm and fill caches. // Prepare intermediate results, run warm and fill caches.
let src = std::fs::read_to_string(&path).unwrap(); let src = std::fs::read_to_string(&path).unwrap();
let tree = Rc::new(parse(&src).output); let tree = Rc::new(parse(&src).output);
let evaluated = eval(&mut loader, &mut cache, &path, tree.clone(), &scope); let evaluated = eval(&mut loader, &mut cache, Some(&path), tree.clone(), &scope);
let executed = exec(&evaluated.output.template, state.clone()); let executed = exec(&evaluated.output.template, state.clone());
let layouted = layout(&mut loader, &mut cache, &executed.output); let layouted = layout(&mut loader, &mut cache, &executed.output);
// Bench! // Bench!
bench!("parse": parse(&src)); bench!("parse": parse(&src));
bench!("eval": eval(&mut loader, &mut cache, &path, tree.clone(), &scope)); bench!("eval": eval(&mut loader, &mut cache, Some(&path), tree.clone(), &scope));
bench!("exec": exec(&evaluated.output.template, state.clone())); bench!("exec": exec(&evaluated.output.template, state.clone()));
bench!("layout": layout(&mut loader, &mut cache, &executed.output)); bench!("layout": layout(&mut loader, &mut cache, &executed.output));
bench!("typeset": typeset(&mut loader, &mut cache, &path, &src, &scope, state.clone())); bench!("typeset": typeset(&mut loader, &mut cache, Some(&path), &src, &scope, state.clone()));
bench!("pdf": pdf(&cache, &layouted)); bench!("pdf": pdf(&cache, &layouted));
} }
} }

View File

@ -17,7 +17,7 @@ impl<'a> CapturesVisitor<'a> {
pub fn new(external: &'a Scopes) -> Self { pub fn new(external: &'a Scopes) -> Self {
Self { Self {
external, external,
internal: Scopes::new(), internal: Scopes::new(None),
captures: Scope::new(), captures: Scope::new(),
} }
} }

View File

@ -23,22 +23,17 @@ use crate::loading::{FileHash, Loader};
use crate::parse::parse; use crate::parse::parse;
use crate::syntax::visit::Visit; use crate::syntax::visit::Visit;
use crate::syntax::*; use crate::syntax::*;
use crate::util::PathExt;
/// Evaluated a parsed source file into a module. /// Evaluate a parsed source file into a module.
///
/// The `path` should point to the source file for the `tree` and is used to
/// resolve relative path names.
///
/// The `scope` consists of the base definitions that are present from the
/// beginning (typically, the standard library).
pub fn eval( pub fn eval(
loader: &mut dyn Loader, loader: &mut dyn Loader,
cache: &mut Cache, cache: &mut Cache,
path: &Path, path: Option<&Path>,
tree: Rc<Tree>, tree: Rc<Tree>,
base: &Scope, scope: &Scope,
) -> Pass<Module> { ) -> Pass<Module> {
let mut ctx = EvalContext::new(loader, cache, path, base); let mut ctx = EvalContext::new(loader, cache, path, scope);
let map = tree.eval(&mut ctx); let map = tree.eval(&mut ctx);
let module = Module { let module = Module {
scope: ctx.scopes.top, scope: ctx.scopes.top,
@ -67,7 +62,7 @@ pub struct EvalContext<'a> {
/// Evaluation diagnostics. /// Evaluation diagnostics.
pub diags: DiagSet, pub diags: DiagSet,
/// The location of the currently evaluated file. /// The location of the currently evaluated file.
pub path: PathBuf, pub path: Option<PathBuf>,
/// The stack of imported files that led to evaluation of the current file. /// The stack of imported files that led to evaluation of the current file.
pub route: Vec<FileHash>, pub route: Vec<FileHash>,
/// A map of loaded module. /// A map of loaded module.
@ -79,20 +74,24 @@ impl<'a> EvalContext<'a> {
pub fn new( pub fn new(
loader: &'a mut dyn Loader, loader: &'a mut dyn Loader,
cache: &'a mut Cache, cache: &'a mut Cache,
path: &Path, path: Option<&Path>,
base: &'a Scope, scope: &'a Scope,
) -> Self { ) -> Self {
let path = path.map(PathExt::normalize);
let mut route = vec![]; let mut route = vec![];
if let Some(path) = &path {
if let Some(hash) = loader.resolve(path) { if let Some(hash) = loader.resolve(path) {
route.push(hash); route.push(hash);
} }
}
Self { Self {
loader, loader,
cache, cache,
scopes: Scopes::with_base(Some(base)), scopes: Scopes::new(Some(scope)),
diags: DiagSet::new(), diags: DiagSet::new(),
path: path.to_owned(), path,
route, route,
modules: HashMap::new(), modules: HashMap::new(),
} }
@ -102,10 +101,13 @@ impl<'a> EvalContext<'a> {
/// ///
/// Generates an error if the file is not found. /// Generates an error if the file is not found.
pub fn resolve(&mut self, path: &str, span: Span) -> Option<(PathBuf, FileHash)> { pub fn resolve(&mut self, path: &str, span: Span) -> Option<(PathBuf, FileHash)> {
let dir = self.path.parent().expect("location is a file"); let path = match &self.path {
let path = dir.join(path); Some(current) => current.parent()?.join(path),
None => PathBuf::from(path),
};
match self.loader.resolve(&path) { match self.loader.resolve(&path) {
Some(hash) => Some((path, hash)), Some(hash) => Some((path.normalize(), hash)),
None => { None => {
self.diag(error!(span, "file not found")); self.diag(error!(span, "file not found"));
None None
@ -142,10 +144,10 @@ impl<'a> EvalContext<'a> {
let parsed = parse(string); let parsed = parse(string);
// Prepare the new context. // Prepare the new context.
let new_scopes = Scopes::with_base(self.scopes.base); let new_scopes = Scopes::new(self.scopes.base);
let old_scopes = mem::replace(&mut self.scopes, new_scopes); let old_scopes = mem::replace(&mut self.scopes, new_scopes);
let old_diags = mem::replace(&mut self.diags, parsed.diags); let old_diags = mem::replace(&mut self.diags, parsed.diags);
let old_path = mem::replace(&mut self.path, resolved); let old_path = mem::replace(&mut self.path, Some(resolved));
self.route.push(hash); self.route.push(hash);
// Evaluate the module. // Evaluate the module.

View File

@ -22,16 +22,7 @@ pub struct Scopes<'a> {
impl<'a> Scopes<'a> { impl<'a> Scopes<'a> {
/// Create a new, empty hierarchy of scopes. /// Create a new, empty hierarchy of scopes.
pub fn new() -> Self { pub fn new(base: Option<&'a Scope>) -> Self {
Self {
top: Scope::new(),
scopes: vec![],
base: None,
}
}
/// Create a new hierarchy of scopes with a base scope.
pub fn with_base(base: Option<&'a Scope>) -> Self {
Self { top: Scope::new(), scopes: vec![], base } Self { top: Scope::new(), scopes: vec![], base }
} }

View File

@ -664,7 +664,6 @@ impl From<AnyValue> for Value {
/// This would allow the type `FontFamily` to be cast from: /// This would allow the type `FontFamily` to be cast from:
/// - a [`Value::Any`] variant already containing a `FontFamily`, /// - a [`Value::Any`] variant already containing a `FontFamily`,
/// - a string, producing a named font family. /// - a string, producing a named font family.
#[macro_export]
macro_rules! value { macro_rules! value {
($type:ty: ($type:ty:
$type_name:literal $type_name:literal

View File

@ -15,9 +15,6 @@ use crate::pretty::pretty;
use crate::syntax::*; use crate::syntax::*;
/// Execute a template to produce a layout tree. /// Execute a template to produce a layout tree.
///
/// The `state` is the base state that may be updated over the course of
/// execution.
pub fn exec(template: &TemplateValue, state: State) -> Pass<layout::Tree> { pub fn exec(template: &TemplateValue, state: State) -> Pass<layout::Tree> {
let mut ctx = ExecContext::new(state); let mut ctx = ExecContext::new(state);
template.exec(&mut ctx); template.exec(&mut ctx);
@ -53,7 +50,7 @@ impl ExecWithMap for Tree {
impl ExecWithMap for Node { impl ExecWithMap for Node {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap) { fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap) {
match self { match self {
Node::Text(text) => ctx.push_text(text.clone()), Node::Text(text) => ctx.push_text(text),
Node::Space => ctx.push_word_space(), Node::Space => ctx.push_word_space(),
_ => map[&(self as *const _)].exec(ctx), _ => map[&(self as *const _)].exec(ctx),
} }
@ -66,7 +63,7 @@ impl Exec for Value {
Value::None => {} Value::None => {}
Value::Int(v) => ctx.push_text(pretty(v)), Value::Int(v) => ctx.push_text(pretty(v)),
Value::Float(v) => ctx.push_text(pretty(v)), Value::Float(v) => ctx.push_text(pretty(v)),
Value::Str(v) => ctx.push_text(v.clone()), Value::Str(v) => ctx.push_text(v),
Value::Template(v) => v.exec(ctx), Value::Template(v) => v.exec(ctx),
Value::Error => {} Value::Error => {}
other => { other => {
@ -93,7 +90,7 @@ impl Exec for TemplateNode {
fn exec(&self, ctx: &mut ExecContext) { fn exec(&self, ctx: &mut ExecContext) {
match self { match self {
Self::Tree { tree, map } => tree.exec_with_map(ctx, &map), Self::Tree { tree, map } => tree.exec_with_map(ctx, &map),
Self::Str(v) => ctx.push_text(v.clone()), Self::Str(v) => ctx.push_text(v),
Self::Func(v) => v.exec(ctx), Self::Func(v) => v.exec(ctx),
} }
} }

View File

@ -139,7 +139,7 @@ impl<'a> PdfExporter<'a> {
// We only write font switching actions when the used face changes. To // We only write font switching actions when the used face changes. To
// do that, we need to remember the active face. // do that, we need to remember the active face.
let mut face = FaceId::MAX; let mut face = None;
let mut size = Length::zero(); let mut size = Length::zero();
let mut fill: Option<Fill> = None; let mut fill: Option<Fill> = None;
@ -158,8 +158,8 @@ impl<'a> PdfExporter<'a> {
// Then, also check if we need to issue a font switching // Then, also check if we need to issue a font switching
// action. // action.
if shaped.face_id != face || shaped.size != size { if face != Some(shaped.face_id) || shaped.size != size {
face = shaped.face_id; face = Some(shaped.face_id);
size = shaped.size; size = shaped.size;
let name = format!("F{}", self.fonts.map(shaped.face_id)); let name = format!("F{}", self.fonts.map(shaped.face_id));

View File

@ -6,8 +6,7 @@ use std::fmt::{self, Debug, Display, Formatter};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::geom::Length; use crate::geom::Length;
use crate::loading::Buffer; use crate::loading::{Buffer, Loader};
use crate::loading::Loader;
/// A font face. /// A font face.
pub struct Face { pub struct Face {
@ -171,7 +170,7 @@ impl FontCache {
let mut families = HashMap::<String, Vec<FaceId>>::new(); let mut families = HashMap::<String, Vec<FaceId>>::new();
for (i, info) in loader.faces().iter().enumerate() { for (i, info) in loader.faces().iter().enumerate() {
let id = FaceId(i as u32); let id = FaceId(i as u64);
faces.push(None); faces.push(None);
families families
.entry(info.family.to_lowercase()) .entry(info.family.to_lowercase())
@ -259,22 +258,19 @@ impl FontCache {
/// A unique identifier for a loaded font face. /// A unique identifier for a loaded font face.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct FaceId(u32); pub struct FaceId(u64);
impl FaceId { impl FaceId {
/// A blank initialization value.
pub const MAX: Self = Self(u32::MAX);
/// Create a face id from the raw underlying value. /// Create a face id from the raw underlying value.
/// ///
/// This should only be called with values returned by /// This should only be called with values returned by
/// [`into_raw`](Self::into_raw). /// [`into_raw`](Self::into_raw).
pub fn from_raw(v: u32) -> Self { pub fn from_raw(v: u64) -> Self {
Self(v) Self(v)
} }
/// Convert into the raw underlying value. /// Convert into the raw underlying value.
pub fn into_raw(self) -> u32 { pub fn into_raw(self) -> u64 {
self.0 self.0
} }
} }

View File

@ -9,7 +9,7 @@ use image::io::Reader as ImageReader;
use image::{DynamicImage, GenericImageView, ImageFormat}; use image::{DynamicImage, GenericImageView, ImageFormat};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::loading::{FileHash, Loader}; use crate::loading::Loader;
/// A loaded image. /// A loaded image.
pub struct Image { pub struct Image {
@ -55,10 +55,8 @@ impl Debug for Image {
/// Caches decoded images. /// Caches decoded images.
pub struct ImageCache { pub struct ImageCache {
/// Loaded images indexed by [`ImageId`].
images: Vec<Image>,
/// Maps from file hashes to ids of decoded images. /// Maps from file hashes to ids of decoded images.
map: HashMap<FileHash, ImageId>, images: HashMap<ImageId, Image>,
/// Callback for loaded images. /// Callback for loaded images.
on_load: Option<Box<dyn Fn(ImageId, &Image)>>, on_load: Option<Box<dyn Fn(ImageId, &Image)>>,
} }
@ -66,28 +64,22 @@ pub struct ImageCache {
impl ImageCache { impl ImageCache {
/// Create a new, empty image cache. /// Create a new, empty image cache.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self { images: HashMap::new(), on_load: None }
images: vec![],
map: HashMap::new(),
on_load: None,
}
} }
/// Load and decode an image file from a path. /// Load and decode an image file from a path.
pub fn load(&mut self, loader: &mut dyn Loader, path: &Path) -> Option<ImageId> { pub fn load(&mut self, loader: &mut dyn Loader, path: &Path) -> Option<ImageId> {
Some(match self.map.entry(loader.resolve(path)?) { let hash = loader.resolve(path)?;
Entry::Occupied(entry) => *entry.get(), let id = ImageId(hash.into_raw());
Entry::Vacant(entry) => { if let Entry::Vacant(entry) = self.images.entry(id) {
let buffer = loader.load_file(path)?; let buffer = loader.load_file(path)?;
let image = Image::parse(&buffer)?; let image = Image::parse(&buffer)?;
let id = ImageId(self.images.len() as u32);
if let Some(callback) = &self.on_load { if let Some(callback) = &self.on_load {
callback(id, &image); callback(id, &image);
} }
self.images.push(image); entry.insert(image);
*entry.insert(id)
} }
}) Some(id)
} }
/// Get a reference to a loaded image. /// Get a reference to a loaded image.
@ -96,7 +88,7 @@ impl ImageCache {
/// only be called with ids returned by [`load()`](Self::load). /// only be called with ids returned by [`load()`](Self::load).
#[track_caller] #[track_caller]
pub fn get(&self, id: ImageId) -> &Image { pub fn get(&self, id: ImageId) -> &Image {
&self.images[id.0 as usize] &self.images[&id]
} }
/// Register a callback which is invoked each time an image is loaded. /// Register a callback which is invoked each time an image is loaded.
@ -110,19 +102,19 @@ impl ImageCache {
/// A unique identifier for a loaded image. /// A unique identifier for a loaded image.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct ImageId(u32); pub struct ImageId(u64);
impl ImageId { impl ImageId {
/// Create an image id from the raw underlying value. /// Create an image id from the raw underlying value.
/// ///
/// This should only be called with values returned by /// This should only be called with values returned by
/// [`into_raw`](Self::into_raw). /// [`into_raw`](Self::into_raw).
pub fn from_raw(v: u32) -> Self { pub fn from_raw(v: u64) -> Self {
Self(v) Self(v)
} }
/// Convert into the raw underlying value. /// Convert into the raw underlying value.
pub fn into_raw(self) -> u32 { pub fn into_raw(self) -> u64 {
self.0 self.0
} }
} }

View File

@ -59,16 +59,33 @@ use crate::layout::Frame;
use crate::loading::Loader; use crate::loading::Loader;
/// Process source code directly into a collection of layouted frames. /// Process source code directly into a collection of layouted frames.
///
/// # Parameters
/// - The `loader` is used to load fonts, images and other source files.
/// - The `cache` stores things that are reusable across several compilations
/// like loaded fonts, decoded images and layouting artifacts.
/// - The `path` should point to the source file if `src` comes from the file
/// system and is used to resolve relative paths (for importing and image
/// loading).
/// - The `src` is the _Typst_ source code to typeset.
/// - The `scope` contains definitions that are available everywhere,
/// typically the standard library.
/// - The `state` defines initial properties for page size, font selection and
/// so on.
///
/// # Return value
/// Returns a vector of frames representing individual pages alongside
/// diagnostic information (errors and warnings).
pub fn typeset( pub fn typeset(
loader: &mut dyn Loader, loader: &mut dyn Loader,
cache: &mut Cache, cache: &mut Cache,
path: &Path, path: Option<&Path>,
src: &str, src: &str,
base: &Scope, scope: &Scope,
state: State, state: State,
) -> Pass<Vec<Frame>> { ) -> Pass<Vec<Frame>> {
let parsed = parse::parse(src); let parsed = parse::parse(src);
let evaluated = eval::eval(loader, cache, path, Rc::new(parsed.output), base); let evaluated = eval::eval(loader, cache, path, Rc::new(parsed.output), scope);
let executed = exec::exec(&evaluated.output.template, state); let executed = exec::exec(&evaluated.output.template, state);
let layouted = layout::layout(loader, cache, &executed.output); let layouted = layout::layout(loader, cache, &executed.output);

View File

@ -160,7 +160,7 @@ pub fn raw(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
ctx.set_monospace(); ctx.set_monospace();
ctx.push_text(text.clone()); ctx.push_text(&text);
ctx.state = snapshot; ctx.state = snapshot;
if block { if block {

View File

@ -24,8 +24,7 @@ pub struct FsLoader {
cache: FileCache, cache: FileCache,
} }
/// Maps from paths to loaded file buffers. When the buffer is `None` the file /// Maps from resolved file hashes to loaded file buffers.
/// does not exist or couldn't be read.
type FileCache = HashMap<FileHash, Buffer>; type FileCache = HashMap<FileHash, Buffer>;
impl FsLoader { impl FsLoader {
@ -169,40 +168,31 @@ impl Loader for FsLoader {
} }
fn load_face(&mut self, idx: usize) -> Option<Buffer> { fn load_face(&mut self, idx: usize) -> Option<Buffer> {
load(&mut self.cache, &self.files[idx]) self.load_file(&self.files[idx].clone())
} }
fn load_file(&mut self, path: &Path) -> Option<Buffer> { fn load_file(&mut self, path: &Path) -> Option<Buffer> {
load(&mut self.cache, path) let hash = self.resolve(path)?;
Some(Rc::clone(match self.cache.entry(hash) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let buffer = std::fs::read(path).ok()?;
entry.insert(Rc::new(buffer))
}
}))
} }
fn resolve(&self, path: &Path) -> Option<FileHash> { fn resolve(&self, path: &Path) -> Option<FileHash> {
hash(path)
}
}
/// Load from the file system using a cache.
fn load(cache: &mut FileCache, path: &Path) -> Option<Buffer> {
Some(match cache.entry(hash(path)?) {
Entry::Occupied(entry) => entry.get().clone(),
Entry::Vacant(entry) => {
let buffer = std::fs::read(path).ok()?;
entry.insert(Rc::new(buffer)).clone()
}
})
}
/// Create a hash that is the same for all paths pointing to the same file.
fn hash(path: &Path) -> Option<FileHash> {
let file = File::open(path).ok()?; let file = File::open(path).ok()?;
let meta = file.metadata().ok()?; let meta = file.metadata().ok()?;
if meta.is_file() { if meta.is_file() {
let handle = Handle::from_file(file).ok()?; let handle = Handle::from_file(file).ok()?;
Some(FileHash(fxhash::hash64(&handle))) Some(FileHash::from_raw(fxhash::hash64(&handle)))
} else { } else {
None None
} }
} }
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -36,7 +36,19 @@ pub trait Loader {
/// ///
/// Should be the same for all paths pointing to the same file. /// Should be the same for all paths pointing to the same file.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct FileHash(pub u64); pub struct FileHash(u64);
impl FileHash {
/// Create an file hash from a raw hash value.
pub fn from_raw(v: u64) -> Self {
Self(v)
}
/// Convert into the raw underlying hash value.
pub fn into_raw(self) -> u64 {
self.0
}
}
/// A loader which serves nothing. /// A loader which serves nothing.
pub struct BlankLoader; pub struct BlankLoader;

View File

@ -2,8 +2,7 @@ use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::{anyhow, bail, Context}; use anyhow::{anyhow, bail, Context};
use same_file::is_same_file;
use typst::loading::Loader;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let args: Vec<_> = std::env::args().collect(); let args: Vec<_> = std::env::args().collect();
@ -33,9 +32,7 @@ fn main() -> anyhow::Result<()> {
}; };
// Ensure that the source file is not overwritten. // Ensure that the source file is not overwritten.
let src_hash = loader.resolve(&src_path); if is_same_file(src_path, &dest_path).unwrap_or(false) {
let dest_hash = loader.resolve(&dest_path);
if src_hash.is_some() && src_hash == dest_hash {
bail!("source and destination files are the same"); bail!("source and destination files are the same");
} }
@ -47,7 +44,14 @@ fn main() -> anyhow::Result<()> {
let mut cache = typst::cache::Cache::new(&loader); let mut cache = typst::cache::Cache::new(&loader);
let scope = typst::library::new(); let scope = typst::library::new();
let state = typst::exec::State::default(); let state = typst::exec::State::default();
let pass = typst::typeset(&mut loader, &mut cache, &src_path, &src, &scope, state); let pass = typst::typeset(
&mut loader,
&mut cache,
Some(&src_path),
&src,
&scope,
state,
);
// Print diagnostics. // Print diagnostics.
let map = typst::parse::LineMap::new(&src); let map = typst::parse::LineMap::new(&src);

View File

@ -2,6 +2,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::ops::Range; use std::ops::Range;
use std::path::{Component, Path, PathBuf};
/// Additional methods for slices. /// Additional methods for slices.
pub trait SliceExt<T> { pub trait SliceExt<T> {
@ -79,3 +80,28 @@ impl RangeExt for Range<usize> {
} }
} }
} }
/// Additional methods for [`Path`].
pub trait PathExt {
/// Lexically normalize a path.
fn normalize(&self) -> PathBuf;
}
impl PathExt for Path {
fn normalize(&self) -> PathBuf {
let mut out = PathBuf::new();
for component in self.components() {
match component {
Component::CurDir => {}
Component::ParentDir => match out.components().next_back() {
Some(Component::Normal(_)) => {
out.pop();
}
_ => out.push(component),
},
_ => out.push(component),
}
}
out
}
}

View File

@ -203,7 +203,7 @@ fn test(
fn test_part( fn test_part(
loader: &mut FsLoader, loader: &mut FsLoader,
cache: &mut Cache, cache: &mut Cache,
path: &Path, src_path: &Path,
src: &str, src: &str,
i: usize, i: usize,
compare_ref: bool, compare_ref: bool,
@ -224,7 +224,7 @@ fn test_part(
state.page.size = Size::new(Length::pt(120.0), Length::raw(f64::INFINITY)); state.page.size = Size::new(Length::pt(120.0), Length::raw(f64::INFINITY));
state.page.margins = Sides::splat(Some(Length::pt(10.0).into())); state.page.margins = Sides::splat(Some(Length::pt(10.0).into()));
let mut pass = typst::typeset(loader, cache, path, &src, &scope, state); let mut pass = typst::typeset(loader, cache, Some(src_path), &src, &scope, state);
if !compare_ref { if !compare_ref {
pass.output.clear(); pass.output.clear();
} }