Absolute paths
This commit is contained in:
parent
6536e9e069
commit
a741bd6b83
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
})?;
|
||||
|
||||
|
@ -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) {
|
||||
|
44
src/lib.rs
44
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.
|
||||
|
@ -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")?;
|
||||
|
19
src/main.rs
19
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).
|
||||
|
@ -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 |
@ -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"
|
||||
|
||||
---
|
||||
|
@ -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"
|
||||
}
|
||||
|
||||
|
@ -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%))
|
||||
|
@ -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")
|
||||
|
||||
---
|
||||
|
@ -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%)
|
||||
)
|
||||
|
||||
---
|
||||
|
@ -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,
|
||||
|
@ -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]
|
||||
|
||||
---
|
||||
|
@ -7,7 +7,7 @@
|
||||
dx: -10pt,
|
||||
dy: -10pt,
|
||||
image(
|
||||
"../../res/tiger.jpg",
|
||||
"/res/tiger.jpg",
|
||||
fit: "cover",
|
||||
width: 100% + 20pt,
|
||||
height: 100% + 20pt,
|
||||
|
@ -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, \
|
||||
|
@ -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) }
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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))
|
||||
])
|
||||
|
Loading…
x
Reference in New Issue
Block a user