Refactor path handling
This commit is contained in:
parent
9bdb0bdeff
commit
7218892c72
@ -6,7 +6,7 @@ edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["cli", "fs"]
|
||||
cli = ["anyhow", "fs"]
|
||||
cli = ["anyhow", "fs", "same-file"]
|
||||
fs = ["dirs", "memmap2", "same-file", "walkdir"]
|
||||
|
||||
[workspace]
|
||||
|
@ -41,16 +41,16 @@ fn benchmarks(c: &mut Criterion) {
|
||||
// Prepare intermediate results, run warm and fill caches.
|
||||
let src = std::fs::read_to_string(&path).unwrap();
|
||||
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 layouted = layout(&mut loader, &mut cache, &executed.output);
|
||||
|
||||
// Bench!
|
||||
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!("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));
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ impl<'a> CapturesVisitor<'a> {
|
||||
pub fn new(external: &'a Scopes) -> Self {
|
||||
Self {
|
||||
external,
|
||||
internal: Scopes::new(),
|
||||
internal: Scopes::new(None),
|
||||
captures: Scope::new(),
|
||||
}
|
||||
}
|
||||
|
@ -23,22 +23,17 @@ use crate::loading::{FileHash, Loader};
|
||||
use crate::parse::parse;
|
||||
use crate::syntax::visit::Visit;
|
||||
use crate::syntax::*;
|
||||
use crate::util::PathExt;
|
||||
|
||||
/// Evaluated 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).
|
||||
/// Evaluate a parsed source file into a module.
|
||||
pub fn eval(
|
||||
loader: &mut dyn Loader,
|
||||
cache: &mut Cache,
|
||||
path: &Path,
|
||||
path: Option<&Path>,
|
||||
tree: Rc<Tree>,
|
||||
base: &Scope,
|
||||
scope: &Scope,
|
||||
) -> 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 module = Module {
|
||||
scope: ctx.scopes.top,
|
||||
@ -67,7 +62,7 @@ pub struct EvalContext<'a> {
|
||||
/// Evaluation diagnostics.
|
||||
pub diags: DiagSet,
|
||||
/// 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.
|
||||
pub route: Vec<FileHash>,
|
||||
/// A map of loaded module.
|
||||
@ -79,20 +74,24 @@ impl<'a> EvalContext<'a> {
|
||||
pub fn new(
|
||||
loader: &'a mut dyn Loader,
|
||||
cache: &'a mut Cache,
|
||||
path: &Path,
|
||||
base: &'a Scope,
|
||||
path: Option<&Path>,
|
||||
scope: &'a Scope,
|
||||
) -> Self {
|
||||
let path = path.map(PathExt::normalize);
|
||||
|
||||
let mut route = vec![];
|
||||
if let Some(hash) = loader.resolve(path) {
|
||||
route.push(hash);
|
||||
if let Some(path) = &path {
|
||||
if let Some(hash) = loader.resolve(path) {
|
||||
route.push(hash);
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
loader,
|
||||
cache,
|
||||
scopes: Scopes::with_base(Some(base)),
|
||||
scopes: Scopes::new(Some(scope)),
|
||||
diags: DiagSet::new(),
|
||||
path: path.to_owned(),
|
||||
path,
|
||||
route,
|
||||
modules: HashMap::new(),
|
||||
}
|
||||
@ -102,10 +101,13 @@ impl<'a> EvalContext<'a> {
|
||||
///
|
||||
/// Generates an error if the file is not found.
|
||||
pub fn resolve(&mut self, path: &str, span: Span) -> Option<(PathBuf, FileHash)> {
|
||||
let dir = self.path.parent().expect("location is a file");
|
||||
let path = dir.join(path);
|
||||
let path = match &self.path {
|
||||
Some(current) => current.parent()?.join(path),
|
||||
None => PathBuf::from(path),
|
||||
};
|
||||
|
||||
match self.loader.resolve(&path) {
|
||||
Some(hash) => Some((path, hash)),
|
||||
Some(hash) => Some((path.normalize(), hash)),
|
||||
None => {
|
||||
self.diag(error!(span, "file not found"));
|
||||
None
|
||||
@ -142,10 +144,10 @@ impl<'a> EvalContext<'a> {
|
||||
let parsed = parse(string);
|
||||
|
||||
// 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_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);
|
||||
|
||||
// Evaluate the module.
|
||||
|
@ -22,16 +22,7 @@ pub struct Scopes<'a> {
|
||||
|
||||
impl<'a> Scopes<'a> {
|
||||
/// Create a new, empty hierarchy of scopes.
|
||||
pub fn new() -> 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 {
|
||||
pub fn new(base: Option<&'a Scope>) -> Self {
|
||||
Self { top: Scope::new(), scopes: vec![], base }
|
||||
}
|
||||
|
||||
|
@ -664,7 +664,6 @@ impl From<AnyValue> for Value {
|
||||
/// This would allow the type `FontFamily` to be cast from:
|
||||
/// - a [`Value::Any`] variant already containing a `FontFamily`,
|
||||
/// - a string, producing a named font family.
|
||||
#[macro_export]
|
||||
macro_rules! value {
|
||||
($type:ty:
|
||||
$type_name:literal
|
||||
|
@ -15,9 +15,6 @@ use crate::pretty::pretty;
|
||||
use crate::syntax::*;
|
||||
|
||||
/// 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> {
|
||||
let mut ctx = ExecContext::new(state);
|
||||
template.exec(&mut ctx);
|
||||
@ -53,7 +50,7 @@ impl ExecWithMap for Tree {
|
||||
impl ExecWithMap for Node {
|
||||
fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap) {
|
||||
match self {
|
||||
Node::Text(text) => ctx.push_text(text.clone()),
|
||||
Node::Text(text) => ctx.push_text(text),
|
||||
Node::Space => ctx.push_word_space(),
|
||||
_ => map[&(self as *const _)].exec(ctx),
|
||||
}
|
||||
@ -66,7 +63,7 @@ impl Exec for Value {
|
||||
Value::None => {}
|
||||
Value::Int(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::Error => {}
|
||||
other => {
|
||||
@ -93,7 +90,7 @@ impl Exec for TemplateNode {
|
||||
fn exec(&self, ctx: &mut ExecContext) {
|
||||
match self {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ impl<'a> PdfExporter<'a> {
|
||||
|
||||
// We only write font switching actions when the used face changes. To
|
||||
// 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 fill: Option<Fill> = None;
|
||||
|
||||
@ -158,8 +158,8 @@ impl<'a> PdfExporter<'a> {
|
||||
|
||||
// Then, also check if we need to issue a font switching
|
||||
// action.
|
||||
if shaped.face_id != face || shaped.size != size {
|
||||
face = shaped.face_id;
|
||||
if face != Some(shaped.face_id) || shaped.size != size {
|
||||
face = Some(shaped.face_id);
|
||||
size = shaped.size;
|
||||
|
||||
let name = format!("F{}", self.fonts.map(shaped.face_id));
|
||||
|
14
src/font.rs
14
src/font.rs
@ -6,8 +6,7 @@ use std::fmt::{self, Debug, Display, Formatter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::geom::Length;
|
||||
use crate::loading::Buffer;
|
||||
use crate::loading::Loader;
|
||||
use crate::loading::{Buffer, Loader};
|
||||
|
||||
/// A font face.
|
||||
pub struct Face {
|
||||
@ -171,7 +170,7 @@ impl FontCache {
|
||||
let mut families = HashMap::<String, Vec<FaceId>>::new();
|
||||
|
||||
for (i, info) in loader.faces().iter().enumerate() {
|
||||
let id = FaceId(i as u32);
|
||||
let id = FaceId(i as u64);
|
||||
faces.push(None);
|
||||
families
|
||||
.entry(info.family.to_lowercase())
|
||||
@ -259,22 +258,19 @@ impl FontCache {
|
||||
|
||||
/// A unique identifier for a loaded font face.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub struct FaceId(u32);
|
||||
pub struct FaceId(u64);
|
||||
|
||||
impl FaceId {
|
||||
/// A blank initialization value.
|
||||
pub const MAX: Self = Self(u32::MAX);
|
||||
|
||||
/// Create a face id from the raw underlying value.
|
||||
///
|
||||
/// This should only be called with values returned by
|
||||
/// [`into_raw`](Self::into_raw).
|
||||
pub fn from_raw(v: u32) -> Self {
|
||||
pub fn from_raw(v: u64) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
|
||||
/// Convert into the raw underlying value.
|
||||
pub fn into_raw(self) -> u32 {
|
||||
pub fn into_raw(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
42
src/image.rs
42
src/image.rs
@ -9,7 +9,7 @@ use image::io::Reader as ImageReader;
|
||||
use image::{DynamicImage, GenericImageView, ImageFormat};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::loading::{FileHash, Loader};
|
||||
use crate::loading::Loader;
|
||||
|
||||
/// A loaded image.
|
||||
pub struct Image {
|
||||
@ -55,10 +55,8 @@ impl Debug for Image {
|
||||
|
||||
/// Caches decoded images.
|
||||
pub struct ImageCache {
|
||||
/// Loaded images indexed by [`ImageId`].
|
||||
images: Vec<Image>,
|
||||
/// Maps from file hashes to ids of decoded images.
|
||||
map: HashMap<FileHash, ImageId>,
|
||||
images: HashMap<ImageId, Image>,
|
||||
/// Callback for loaded images.
|
||||
on_load: Option<Box<dyn Fn(ImageId, &Image)>>,
|
||||
}
|
||||
@ -66,28 +64,22 @@ pub struct ImageCache {
|
||||
impl ImageCache {
|
||||
/// Create a new, empty image cache.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
images: vec![],
|
||||
map: HashMap::new(),
|
||||
on_load: None,
|
||||
}
|
||||
Self { images: HashMap::new(), on_load: None }
|
||||
}
|
||||
|
||||
/// Load and decode an image file from a path.
|
||||
pub fn load(&mut self, loader: &mut dyn Loader, path: &Path) -> Option<ImageId> {
|
||||
Some(match self.map.entry(loader.resolve(path)?) {
|
||||
Entry::Occupied(entry) => *entry.get(),
|
||||
Entry::Vacant(entry) => {
|
||||
let buffer = loader.load_file(path)?;
|
||||
let image = Image::parse(&buffer)?;
|
||||
let id = ImageId(self.images.len() as u32);
|
||||
if let Some(callback) = &self.on_load {
|
||||
callback(id, &image);
|
||||
}
|
||||
self.images.push(image);
|
||||
*entry.insert(id)
|
||||
let hash = loader.resolve(path)?;
|
||||
let id = ImageId(hash.into_raw());
|
||||
if let Entry::Vacant(entry) = self.images.entry(id) {
|
||||
let buffer = loader.load_file(path)?;
|
||||
let image = Image::parse(&buffer)?;
|
||||
if let Some(callback) = &self.on_load {
|
||||
callback(id, &image);
|
||||
}
|
||||
})
|
||||
entry.insert(image);
|
||||
}
|
||||
Some(id)
|
||||
}
|
||||
|
||||
/// Get a reference to a loaded image.
|
||||
@ -96,7 +88,7 @@ impl ImageCache {
|
||||
/// only be called with ids returned by [`load()`](Self::load).
|
||||
#[track_caller]
|
||||
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.
|
||||
@ -110,19 +102,19 @@ impl ImageCache {
|
||||
|
||||
/// A unique identifier for a loaded image.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub struct ImageId(u32);
|
||||
pub struct ImageId(u64);
|
||||
|
||||
impl ImageId {
|
||||
/// Create an image id from the raw underlying value.
|
||||
///
|
||||
/// This should only be called with values returned by
|
||||
/// [`into_raw`](Self::into_raw).
|
||||
pub fn from_raw(v: u32) -> Self {
|
||||
pub fn from_raw(v: u64) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
|
||||
/// Convert into the raw underlying value.
|
||||
pub fn into_raw(self) -> u32 {
|
||||
pub fn into_raw(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
23
src/lib.rs
23
src/lib.rs
@ -59,16 +59,33 @@ use crate::layout::Frame;
|
||||
use crate::loading::Loader;
|
||||
|
||||
/// 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(
|
||||
loader: &mut dyn Loader,
|
||||
cache: &mut Cache,
|
||||
path: &Path,
|
||||
path: Option<&Path>,
|
||||
src: &str,
|
||||
base: &Scope,
|
||||
scope: &Scope,
|
||||
state: State,
|
||||
) -> Pass<Vec<Frame>> {
|
||||
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 layouted = layout::layout(loader, cache, &executed.output);
|
||||
|
||||
|
@ -160,7 +160,7 @@ pub fn raw(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||
|
||||
let snapshot = ctx.state.clone();
|
||||
ctx.set_monospace();
|
||||
ctx.push_text(text.clone());
|
||||
ctx.push_text(&text);
|
||||
ctx.state = snapshot;
|
||||
|
||||
if block {
|
||||
|
@ -24,8 +24,7 @@ pub struct FsLoader {
|
||||
cache: FileCache,
|
||||
}
|
||||
|
||||
/// Maps from paths to loaded file buffers. When the buffer is `None` the file
|
||||
/// does not exist or couldn't be read.
|
||||
/// Maps from resolved file hashes to loaded file buffers.
|
||||
type FileCache = HashMap<FileHash, Buffer>;
|
||||
|
||||
impl FsLoader {
|
||||
@ -169,38 +168,29 @@ impl Loader for FsLoader {
|
||||
}
|
||||
|
||||
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> {
|
||||
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> {
|
||||
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()
|
||||
let file = File::open(path).ok()?;
|
||||
let meta = file.metadata().ok()?;
|
||||
if meta.is_file() {
|
||||
let handle = Handle::from_file(file).ok()?;
|
||||
Some(FileHash::from_raw(fxhash::hash64(&handle)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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 meta = file.metadata().ok()?;
|
||||
if meta.is_file() {
|
||||
let handle = Handle::from_file(file).ok()?;
|
||||
Some(FileHash(fxhash::hash64(&handle)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,19 @@ pub trait Loader {
|
||||
///
|
||||
/// Should be the same for all paths pointing to the same file.
|
||||
#[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.
|
||||
pub struct BlankLoader;
|
||||
|
16
src/main.rs
16
src/main.rs
@ -2,8 +2,7 @@ use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
|
||||
use typst::loading::Loader;
|
||||
use same_file::is_same_file;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args: Vec<_> = std::env::args().collect();
|
||||
@ -33,9 +32,7 @@ fn main() -> anyhow::Result<()> {
|
||||
};
|
||||
|
||||
// Ensure that the source file is not overwritten.
|
||||
let src_hash = loader.resolve(&src_path);
|
||||
let dest_hash = loader.resolve(&dest_path);
|
||||
if src_hash.is_some() && src_hash == dest_hash {
|
||||
if is_same_file(src_path, &dest_path).unwrap_or(false) {
|
||||
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 scope = typst::library::new();
|
||||
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.
|
||||
let map = typst::parse::LineMap::new(&src);
|
||||
|
26
src/util.rs
26
src/util.rs
@ -2,6 +2,7 @@
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Range;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
/// Additional methods for slices.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ fn test(
|
||||
fn test_part(
|
||||
loader: &mut FsLoader,
|
||||
cache: &mut Cache,
|
||||
path: &Path,
|
||||
src_path: &Path,
|
||||
src: &str,
|
||||
i: usize,
|
||||
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.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 {
|
||||
pass.output.clear();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user