Absolute paths

This commit is contained in:
Laurenz 2022-05-16 17:56:23 +02:00
parent 6536e9e069
commit a741bd6b83
23 changed files with 123 additions and 60 deletions

View File

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

View File

@ -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),
})?;

View File

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

View File

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

View File

@ -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")?;

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@
dx: -10pt,
dy: -10pt,
image(
"../../res/tiger.jpg",
"/res/tiger.jpg",
fit: "cover",
width: 100% + 20pt,
height: 100% + 20pt,

View File

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

View File

@ -36,6 +36,18 @@ Hello *{x}*
[Not blue]
}
---
// 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) }

View File

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

View File

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

View File

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

View File

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