summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-05-16 17:56:23 +0200
committerLaurenz <laurmaedje@gmail.com>2022-05-16 17:56:36 +0200
commita741bd6b83d1e374c8218b5439e26522499cc4ae (patch)
tree796ef8b8ae2186a082f37a2aa4732c9bba7d2bdf
parent6536e9e069616b862ebb774c7bef1b886c069350 (diff)
Absolute paths
-rw-r--r--src/eval/func.rs21
-rw-r--r--src/eval/mod.rs8
-rw-r--r--src/image.rs3
-rw-r--r--src/lib.rs44
-rw-r--r--src/library/graphics/image.rs12
-rw-r--r--src/main.rs19
-rw-r--r--src/source.rs3
-rw-r--r--tests/ref/style/set.pngbin14415 -> 20833 bytes
-rw-r--r--tests/ref/style/show-text.pngbin39399 -> 43671 bytes
-rw-r--r--tests/typ/code/import.typ4
-rw-r--r--tests/typ/code/include.typ4
-rw-r--r--tests/typ/coma.typ2
-rw-r--r--tests/typ/graphics/image.typ24
-rw-r--r--tests/typ/graphics/transform.typ4
-rw-r--r--tests/typ/layout/grid-3.typ2
-rw-r--r--tests/typ/layout/pad.typ2
-rw-r--r--tests/typ/layout/place-background.typ2
-rw-r--r--tests/typ/layout/place.typ2
-rw-r--r--tests/typ/style/set.typ12
-rw-r--r--tests/typ/style/show-text.typ7
-rw-r--r--tests/typ/text/bidi.typ2
-rw-r--r--tests/typ/text/indent.typ4
-rw-r--r--tests/typ/text/link.typ2
23 files changed, 123 insertions, 60 deletions
diff --git a/src/eval/func.rs b/src/eval/func.rs
index 4c5761ab..f15b0241 100644
--- a/src/eval/func.rs
+++ b/src/eval/func.rs
@@ -5,6 +5,7 @@ use std::sync::Arc;
use super::{Args, Eval, Flow, Scope, Scopes, Value};
use crate::diag::{StrResult, TypResult};
use crate::model::{Content, NodeId, StyleMap};
+use crate::source::SourceId;
use crate::syntax::ast::Expr;
use crate::util::EcoString;
use crate::Context;
@@ -174,6 +175,8 @@ pub trait Node: 'static {
/// A user-defined closure.
#[derive(Hash)]
pub struct Closure {
+ /// The location where the closure was defined.
+ pub location: Option<SourceId>,
/// The name of the closure.
pub name: Option<EcoString>,
/// Captured values from outer scopes.
@@ -212,18 +215,28 @@ impl Closure {
// Backup the old control flow state.
let prev_flow = ctx.flow.take();
+ let detached = ctx.route.is_empty();
+ if detached {
+ ctx.route = self.location.into_iter().collect();
+ }
// Evaluate the body.
- let mut value = self.body.eval(ctx, &mut scp)?;
+ let result = self.body.eval(ctx, &mut scp);
+
+ // Restore the old control flow state.
+ let flow = std::mem::replace(&mut ctx.flow, prev_flow);
+ if detached {
+ ctx.route.clear();
+ }
// Handle control flow.
- match std::mem::replace(&mut ctx.flow, prev_flow) {
- Some(Flow::Return(_, Some(explicit))) => value = explicit,
+ match flow {
+ Some(Flow::Return(_, Some(explicit))) => return Ok(explicit),
Some(Flow::Return(_, None)) => {}
Some(flow) => return Err(flow.forbidden())?,
None => {}
}
- Ok(value)
+ result
}
}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index b35cf1ef..79060137 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -707,6 +707,7 @@ impl Eval for ClosureExpr {
// Define the actual function.
Ok(Value::Func(Func::from_closure(Closure {
+ location: ctx.route.last().copied(),
name,
captured,
params,
@@ -765,6 +766,7 @@ impl Eval for ShowExpr {
let body = self.body();
let span = body.span();
let func = Func::from_closure(Closure {
+ location: ctx.route.last().copied(),
name: None,
captured,
params,
@@ -945,9 +947,11 @@ impl Eval for IncludeExpr {
/// Process an import of a module relative to the current location.
fn import(ctx: &mut Context, path: &str, span: Span) -> TypResult<Module> {
// Load the source file.
- let full = ctx.complete_path(path);
+ let full = ctx.locate(&path).at(span)?;
let id = ctx.sources.load(&full).map_err(|err| match err.kind() {
- std::io::ErrorKind::NotFound => error!(span, "file not found"),
+ std::io::ErrorKind::NotFound => {
+ error!(span, "file not found (searched at {})", full.display())
+ }
_ => error!(span, "failed to load source file ({})", err),
})?;
diff --git a/src/image.rs b/src/image.rs
index 24a0deec..87c093d3 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -48,7 +48,8 @@ impl ImageStore {
}
}
- /// Load and decode an image file from a path.
+ /// Load and decode an image file from a path relative to the compilation
+ /// environment's root.
pub fn load(&mut self, path: &Path) -> io::Result<ImageId> {
let hash = self.loader.resolve(path)?;
Ok(*match self.files.entry(hash) {
diff --git a/src/lib.rs b/src/lib.rs
index efddc239..eb6e8f72 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -57,7 +57,7 @@ use std::hash::Hash;
use std::path::PathBuf;
use std::sync::Arc;
-use crate::diag::TypResult;
+use crate::diag::{StrResult, TypResult};
use crate::eval::{Eval, Flow, Module, Scope, Scopes};
use crate::font::FontStore;
use crate::frame::Frame;
@@ -65,6 +65,7 @@ use crate::image::ImageStore;
use crate::loading::Loader;
use crate::model::StyleMap;
use crate::source::{SourceId, SourceStore};
+use crate::util::PathExt;
/// The core context which holds the loader, configuration and cached artifacts.
pub struct Context {
@@ -76,6 +77,8 @@ pub struct Context {
pub fonts: FontStore,
/// Stores decoded images.
pub images: ImageStore,
+ /// The compilation root.
+ root: PathBuf,
/// The standard library scope.
std: Arc<Scope>,
/// The default styles.
@@ -172,51 +175,64 @@ impl Context {
self.evaluate(id)?.content.layout(self)
}
- /// Resolve a user-entered path (relative to the current evaluation
- /// location) to be relative to the compilation environment's root.
- pub fn complete_path(&self, path: &str) -> PathBuf {
+ /// Resolve a user-entered path to be relative to the compilation
+ /// environment's root.
+ pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
if let Some(&id) = self.route.last() {
+ if let Some(path) = path.strip_prefix('/') {
+ return Ok(self.root.join(path).normalize());
+ }
+
if let Some(dir) = self.sources.get(id).path().parent() {
- return dir.join(path);
+ return Ok(dir.join(path).normalize());
}
}
- path.into()
+ return Err("cannot access file system from here".into());
}
}
/// A builder for a [`Context`].
///
/// This struct is created by [`Context::builder`].
+#[derive(Default)]
pub struct ContextBuilder {
+ root: PathBuf,
std: Option<Arc<Scope>>,
styles: Option<Arc<StyleMap>>,
}
impl ContextBuilder {
+ /// The compilation root, relative to which absolute paths are.
+ pub fn root(&mut self, root: impl Into<PathBuf>) -> &mut Self {
+ self.root = root.into();
+ self
+ }
+
/// The scope containing definitions that are available everywhere
/// (the standard library).
- pub fn std(mut self, std: impl Into<Arc<Scope>>) -> Self {
+ pub fn std(&mut self, std: impl Into<Arc<Scope>>) -> &mut Self {
self.std = Some(std.into());
self
}
/// The default properties for page size, font selection and so on.
- pub fn styles(mut self, styles: impl Into<Arc<StyleMap>>) -> Self {
+ pub fn styles(&mut self, styles: impl Into<Arc<StyleMap>>) -> &mut Self {
self.styles = Some(styles.into());
self
}
/// Finish building the context by providing the `loader` used to load
/// fonts, images, source files and other resources.
- pub fn build(self, loader: Arc<dyn Loader>) -> Context {
+ pub fn build(&self, loader: Arc<dyn Loader>) -> Context {
Context {
sources: SourceStore::new(Arc::clone(&loader)),
fonts: FontStore::new(Arc::clone(&loader)),
images: ImageStore::new(Arc::clone(&loader)),
loader,
- std: self.std.unwrap_or_else(|| Arc::new(library::new())),
- styles: self.styles.unwrap_or_default(),
+ root: self.root.clone(),
+ std: self.std.clone().unwrap_or_else(|| Arc::new(library::new())),
+ styles: self.styles.clone().unwrap_or_default(),
modules: HashMap::new(),
cache: HashMap::new(),
route: vec![],
@@ -226,12 +242,6 @@ impl ContextBuilder {
}
}
-impl Default for ContextBuilder {
- fn default() -> Self {
- Self { std: None, styles: None }
- }
-}
-
/// An entry in the query cache.
struct CacheEntry {
/// The query's results.
diff --git a/src/library/graphics/image.rs b/src/library/graphics/image.rs
index ee854130..6fd465cb 100644
--- a/src/library/graphics/image.rs
+++ b/src/library/graphics/image.rs
@@ -12,11 +12,15 @@ impl ImageNode {
pub const FIT: ImageFit = ImageFit::Cover;
fn construct(ctx: &mut Context, args: &mut Args) -> TypResult<Content> {
- let path = args.expect::<Spanned<EcoString>>("path to image file")?;
- let full = ctx.complete_path(&path.v);
+ let Spanned { v: path, span } =
+ args.expect::<Spanned<EcoString>>("path to image file")?;
+
+ let full = ctx.locate(&path).at(span)?;
let id = ctx.images.load(&full).map_err(|err| match err.kind() {
- std::io::ErrorKind::NotFound => error!(path.span, "file not found"),
- _ => error!(path.span, "failed to load image ({})", err),
+ std::io::ErrorKind::NotFound => {
+ error!(span, "file not found (searched at {})", full.display())
+ }
+ _ => error!(span, "failed to load image ({})", err),
})?;
let width = args.named("width")?;
diff --git a/src/main.rs b/src/main.rs
index daeff033..59ad5a71 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -22,9 +22,10 @@ USAGE:
OPTIONS:
-h, --help Print this help
+ --root <dir> Configure the root for absolute paths
ARGS:
- <input.typ> Path input Typst file
+ <input.typ> Path to input Typst file
[output.pdf] Path to output PDF
";
@@ -44,9 +45,17 @@ fn main() {
fn try_main(args: Args) -> Result<(), String> {
// Create a loader for fonts and files.
let mut loader = FsLoader::new();
+ let mut builder = Context::builder();
+ if let Some(root) = &args.root {
+ builder.root(root);
+ }
// Search for fonts in the project directory.
if let Some(dir) = args.input.parent() {
+ if args.root.is_none() {
+ builder.root(dir);
+ }
+
if dir.as_os_str().is_empty() {
// Just a filename, so directory is current directory.
loader.search_path(".");
@@ -60,7 +69,7 @@ fn try_main(args: Args) -> Result<(), String> {
// Create the context which holds loaded source files, fonts, images and
// cached artifacts.
- let mut ctx = Context::new(loader.wrap());
+ let mut ctx = builder.build(loader.wrap());
// Ensure that the source file is not overwritten.
if is_same_file(&args.input, &args.output).unwrap_or(false) {
@@ -94,6 +103,7 @@ fn try_main(args: Args) -> Result<(), String> {
struct Args {
input: PathBuf,
output: PathBuf,
+ root: Option<PathBuf>,
}
/// Parse command line arguments.
@@ -104,7 +114,8 @@ fn parse_args() -> Result<Args, String> {
std::process::exit(0);
}
- let input = args.free_from_str::<PathBuf>().map_err(|_| "missing input file")?;
+ let root = args.opt_value_from_str("--root").map_err(|_| "malformed root")?;
+ let input: PathBuf = args.free_from_str().map_err(|_| "missing input file")?;
let output = match args.opt_free_from_str().ok().flatten() {
Some(output) => output,
None => {
@@ -118,7 +129,7 @@ fn parse_args() -> Result<Args, String> {
Err("too many arguments")?;
}
- Ok(Args { input, output })
+ Ok(Args { input, output, root })
}
/// Print an application-level error (independent from a source file).
diff --git a/src/source.rs b/src/source.rs
index a7c95255..780e12a8 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -54,7 +54,8 @@ impl SourceStore {
}
}
- /// Load a source file from a path using the `loader`.
+ /// Load a source file from a path relative to the compilation environment's
+ /// root.
///
/// If there already exists a source file for this path, it is
/// [replaced](SourceFile::replace).
diff --git a/tests/ref/style/set.png b/tests/ref/style/set.png
index c63ddb7c..52512b85 100644
--- a/tests/ref/style/set.png
+++ b/tests/ref/style/set.png
Binary files differ
diff --git a/tests/ref/style/show-text.png b/tests/ref/style/show-text.png
index 53c9d132..ae5230f1 100644
--- a/tests/ref/style/show-text.png
+++ b/tests/ref/style/show-text.png
Binary files differ
diff --git a/tests/typ/code/import.typ b/tests/typ/code/import.typ
index dab91dae..aef5e9c4 100644
--- a/tests/typ/code/import.typ
+++ b/tests/typ/code/import.typ
@@ -38,11 +38,11 @@
#import a, c, from "target.typ"
---
-// Error: 19-21 file not found
+// Error: 19-21 file not found (searched at typ/code)
#import name from ""
---
-// Error: 16-27 file not found
+// Error: 16-27 file not found (searched at typ/code/lib/0.2.1)
#import * from "lib/0.2.1"
---
diff --git a/tests/typ/code/include.typ b/tests/typ/code/include.typ
index 3510cb06..cd3328a2 100644
--- a/tests/typ/code/include.typ
+++ b/tests/typ/code/include.typ
@@ -6,7 +6,7 @@
= Document
// Include a file
-#include "importable/chap1.typ"
+#include "/typ/code/importable/chap1.typ"
// Expression as a file name.
#let chap2 = include "import" + "able/chap" + "2.typ"
@@ -16,7 +16,7 @@
---
{
- // Error: 19-41 file not found
+ // Error: 19-41 file not found (searched at typ/code/importable/chap3.typ)
let x = include "importable/chap3.typ"
}
diff --git a/tests/typ/coma.typ b/tests/typ/coma.typ
index 0e228d14..e312fc78 100644
--- a/tests/typ/coma.typ
+++ b/tests/typ/coma.typ
@@ -21,4 +21,4 @@ Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel
zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges
von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel.
-#align(center, image("../res/graph.png", width: 75%))
+#align(center, image("/res/graph.png", width: 75%))
diff --git a/tests/typ/graphics/image.typ b/tests/typ/graphics/image.typ
index 8817713f..e64b6c45 100644
--- a/tests/typ/graphics/image.typ
+++ b/tests/typ/graphics/image.typ
@@ -4,7 +4,7 @@
// Test loading different image formats.
// Load an RGBA PNG image.
-#image("../../res/rhino.png")
+#image("/res/rhino.png")
// Load an RGB JPEG image.
#set page(height: 60pt)
@@ -14,14 +14,14 @@
// Test configuring the size and fitting behaviour of images.
// Set width and height explicitly.
-#image("../../res/rhino.png", width: 30pt)
-#image("../../res/rhino.png", height: 30pt)
+#image("/res/rhino.png", width: 30pt)
+#image("/res/rhino.png", height: 30pt)
// Set width and height explicitly and force stretching.
-#image("../../res/monkey.svg", width: 100%, height: 20pt, fit: "stretch")
+#image("/res/monkey.svg", width: 100%, height: 20pt, fit: "stretch")
// Make sure the bounding-box of the image is correct.
-#align(bottom + right, image("../../res/tiger.jpg", width: 40pt))
+#align(bottom + right, image("/res/tiger.jpg", width: 40pt))
---
// Test all three fit modes.
@@ -30,9 +30,9 @@
columns: (1fr, 1fr, 1fr),
rows: 100%,
gutter: 3pt,
- image("../../res/tiger.jpg", width: 100%, height: 100%, fit: "contain"),
- image("../../res/tiger.jpg", width: 100%, height: 100%, fit: "cover"),
- image("../../res/monkey.svg", width: 100%, height: 100%, fit: "stretch"),
+ image("/res/tiger.jpg", width: 100%, height: 100%, fit: "contain"),
+ image("/res/tiger.jpg", width: 100%, height: 100%, fit: "cover"),
+ image("/res/monkey.svg", width: 100%, height: 100%, fit: "stretch"),
)
---
@@ -40,18 +40,18 @@
#set page(height: 60pt)
Stuff \
Stuff
-#image("../../res/rhino.png")
+#image("/res/rhino.png")
---
// Test baseline.
-A #image("../../res/tiger.jpg", height: 1cm, width: 80%) B
+A #image("/res/tiger.jpg", height: 1cm, width: 80%) B
---
// Test advanced SVG features.
-#image("../../res/pattern.svg")
+#image("/res/pattern.svg")
---
-// Error: 8-29 file not found
+// Error: 8-29 file not found (searched at typ/graphics/path/does/not/exist)
#image("path/does/not/exist")
---
diff --git a/tests/typ/graphics/transform.typ b/tests/typ/graphics/transform.typ
index e0679306..5d2a1729 100644
--- a/tests/typ/graphics/transform.typ
+++ b/tests/typ/graphics/transform.typ
@@ -31,13 +31,13 @@ nor #xetex!
// Test combination of scaling and rotation.
#set page(height: 80pt)
#align(center + horizon,
- rotate(20deg, scale(70%, image("../../res/tiger.jpg")))
+ rotate(20deg, scale(70%, image("/res/tiger.jpg")))
)
---
// Test setting rotation origin.
#rotate(10deg, origin: top + left,
- image("../../res/tiger.jpg", width: 50%)
+ image("/res/tiger.jpg", width: 50%)
)
---
diff --git a/tests/typ/layout/grid-3.typ b/tests/typ/layout/grid-3.typ
index 8d04722e..6b7dc47f 100644
--- a/tests/typ/layout/grid-3.typ
+++ b/tests/typ/layout/grid-3.typ
@@ -23,7 +23,7 @@
columns: 4 * (1fr,),
row-gutter: 10pt,
column-gutter: (0pt, 10%),
- align(top, image("../../res/rhino.png")),
+ align(top, image("/res/rhino.png")),
align(top, rect(fill: eastern, align(right)[LoL])),
[rofl],
[\ A] * 3,
diff --git a/tests/typ/layout/pad.typ b/tests/typ/layout/pad.typ
index 502137ec..9791eae4 100644
--- a/tests/typ/layout/pad.typ
+++ b/tests/typ/layout/pad.typ
@@ -21,7 +21,7 @@ Hi #box(pad(left: 10pt)[A]) there
// Test that the pad node doesn't consume the whole region.
#set page(height: 6cm)
#align(left)[Before]
-#pad(10pt, image("../../res/tiger.jpg"))
+#pad(10pt, image("/res/tiger.jpg"))
#align(right)[After]
---
diff --git a/tests/typ/layout/place-background.typ b/tests/typ/layout/place-background.typ
index f64bf0ee..fafedec1 100644
--- a/tests/typ/layout/place-background.typ
+++ b/tests/typ/layout/place-background.typ
@@ -7,7 +7,7 @@
dx: -10pt,
dy: -10pt,
image(
- "../../res/tiger.jpg",
+ "/res/tiger.jpg",
fit: "cover",
width: 100% + 20pt,
height: 100% + 20pt,
diff --git a/tests/typ/layout/place.typ b/tests/typ/layout/place.typ
index 95049bdc..58f9d06d 100644
--- a/tests/typ/layout/place.typ
+++ b/tests/typ/layout/place.typ
@@ -5,7 +5,7 @@
#place(bottom + center)[© Typst]
= Placement
-#place(right, image("../../res/tiger.jpg", width: 1.8cm))
+#place(right, image("/res/tiger.jpg", width: 1.8cm))
Hi there. This is \
a placed node. \
Unfortunately, \
diff --git a/tests/typ/style/set.typ b/tests/typ/style/set.typ
index 2c12d3e9..830c6661 100644
--- a/tests/typ/style/set.typ
+++ b/tests/typ/style/set.typ
@@ -37,5 +37,17 @@ Hello *{x}*
}
---
+// Test relative path resolving in layout phase.
+#let choice = ("monkey.svg", "rhino.png", "tiger.jpg")
+#set enum(label: n => {
+ let path = "../../res/" + choice(n - 1)
+ move(dy: -0.15em, image(path, width: 1em, height: 1em))
+})
+
+. Monkey
+. Rhino
+. Tiger
+
+---
// Error: 11-25 set is only allowed directly in code and content blocks
{ let x = set text(blue) }
diff --git a/tests/typ/style/show-text.typ b/tests/typ/style/show-text.typ
index f4ecb7e1..283a2887 100644
--- a/tests/typ/style/show-text.typ
+++ b/tests/typ/style/show-text.typ
@@ -56,3 +56,10 @@ Rust is memory-safe and blazingly fast. Let's rewrite everything in rust.
World
- World
+
+---
+// Test absolute path in layout phase.
+
+#show "GRAPH" as image("/res/graph.png")
+
+The GRAPH has nodes.
diff --git a/tests/typ/text/bidi.typ b/tests/typ/text/bidi.typ
index 7058638a..11c0cafa 100644
--- a/tests/typ/text/bidi.typ
+++ b/tests/typ/text/bidi.typ
@@ -43,7 +43,7 @@ Lריווח #h(1cm) R
---
// Test inline object.
#set text(lang: "he", "IBM Plex Serif")
-קרנפיםRh#image("../../res/rhino.png", height: 11pt)inoחיים
+קרנפיםRh#image("/res/rhino.png", height: 11pt)inoחיים
---
// Test whether L1 whitespace resetting destroys stuff.
diff --git a/tests/typ/text/indent.typ b/tests/typ/text/indent.typ
index 897e360c..92b12180 100644
--- a/tests/typ/text/indent.typ
+++ b/tests/typ/text/indent.typ
@@ -8,10 +8,10 @@ The first paragraph has no indent.
But the second one does.
-#image("../../res/tiger.jpg", height: 6pt)
+#image("/res/tiger.jpg", height: 6pt)
starts a paragraph without indent.
-#align(center, image("../../res/rhino.png", width: 1cm))
+#align(center, image("/res/rhino.png", width: 1cm))
= Headings
- And lists.
diff --git a/tests/typ/text/link.typ b/tests/typ/text/link.typ
index ad538187..64697bff 100644
--- a/tests/typ/text/link.typ
+++ b/tests/typ/text/link.typ
@@ -31,5 +31,5 @@ My cool #move(dx: 0.7cm, dy: 0.7cm, rotate(10deg, scale(200%, mylink)))
// Link containing a block.
#link("https://example.com/", underline: false, block[
My cool rhino
- #move(dx: 10pt, image("../../res/rhino.png", width: 1cm))
+ #move(dx: 10pt, image("/res/rhino.png", width: 1cm))
])