Why are paths always relative to the current file?

Context: I was trying to implement a small function that reads and parses a file with some regex. The function just takes the path to the file and returns the parsed context as dictionary. I wanted to move this function into a package.

/somewhere/else/lib.typ

#let parse-xyz(path) = {
    let data = read(path)
    …
}

main.typ

#let values = import-xyz("my/path.xyz");

After that I found out that the read call resolves the path relative to the file that contains the function (lib.typ) and not relative to the main.typ file that calls it. This is different from python and other scripting languages.

1 Like

Hello, if you start your path with "/" instead, the path should resolve relative to --root. The documentation should be updated to reflect this soon!

#import "/templates/lib.typ" will load the file templates/lib.typ relative to the --root .

[1] syntax for marking project root · Issue #2608 · typst/typst · GitHub
[2] Clarify the path argument of the image element by kravchenkoloznia · Pull Request #4892 · typst/typst · GitHub

3 Likes

Alternatively, if you need to use the relative path which isn’t necessarily the same as root-relative path, then you would have to use read("my/path.xyz") as an argument to the import-xyz() function.

You can add #let r = read to make import-xyz(r("my/path.xyz")), which will shorten the workaround code but most certainly reduce readability.

I did face this problem in the past and had a few conversations about this (I think with @laurmaedje), but I don’t remember if this is a “limitation” or a “feature”.

The original reason why it is this way is that it’s made for markup and not code: When writing chapters of text, it is very natural to be able to use relative paths to include other chapters. Absolute paths with a leading slash provide an alternative, primarily useful for importing files shared across a whole project.

However, as you’ve noticed, neither of those two properly handle the case of relative paths passed to utility functions or of any kind of path passed into a package. To properly solve this, we need a path type that is separate from a plain string and that remembers where it was created. Ideally, this path would be supplemented by syntax since its much more natural for syntax to be file-dependant than for a path function.

For more discussion on this, see:

2 Likes

There actually is a way to work around this (and to be clear: it is hacky and kinda works by accident. it will probably work until the path work mentioned by Laurenz is done, but no guarantees.)

Here it is:

// /somewhere/else/lib.typ
#let parse-xyz(..args) = {
    let data = read(..args)
    …
}

 

// main.typ
#let values = parse-xyz("my/path.xyz");

args is an arguments value, and such a value apparently remembers where it was constructed. So when read() gets all the arguments that parse-xyz() receives, these arguments are still relative to main.typ. Whether the loss in readability due to the arcaneness of this behavior is worth it for you is for you to decide :wink:

3 Likes

Nice hack, but to be honest I don’t think I like this :see_no_evil:
Why does this even work?!?

Arguments retain their source location for error messages and the same source location mechanism is used to resolve relative paths.

Glad that I find a solution here, but this feels…very unintuitive since nearly all linux file systems (or windows nowadays) treat path that starts with a “/” as an absolute path but not a relative path.

If you want to stay with a Linux analogy, you can think of a Typst project’s sandbox being similar to a chroot environment. Once you execute chroot, an absolute path will be relative to a directory other than what is globally considered the filesystem root.

Re Windows, isn’t an absolute path still relative to the current drive’s root? So absolute Windows paths also require some context to figure out their meaning.

1 Like

Ok, but this is stupid. One might have figures in another folder. And typst forces you to duplicate your files to access them.

Not to mention that absolutely no one uses “absolute path” to refer to a path that is actually relative to the root of the typist file.

I spent 2 hours trying to figure this out, thinking it is a VS Code issue…

Either way, I find it absolutely impossible to believe that you believe this is what an absolute path refers to in programming.

I don’t see anywhere in this thread where @ensko says that Typst uses absolute paths. They give a few examples where, in Linux, you can enter something formatted as an absolute path and the result is something that is relative to another path. I read this as a justification of the syntax Typst uses.

Whether this is the best option is another question. As I understand it, the main motivation for doing it this way is for security. If a Typst file could have arbitrary file system access it would introduce potential vulnerabilities.


As for duplicating files, I think it’s possible to create a symlink in the Typst project directory that points to your media files.
Another alternative is to create simple script that copies all your media files and then runs the Typst compiler. There are still duplicates here but at least it is automated and less likely to be error prone. You could also delete the duplicates in the same script once Typst is done running.
This way does not benefit from the -watch flag for the compiler.

2 Likes