typst_shim/
path.rs

1//! Compatibility helpers for Typst path resolution.
2
3use typst::diag::HintedStrResult;
4use typst::foundations::PathOrStr;
5use typst::syntax::{FileId, RootedPath};
6
7/// Resolves a Typst path string relative to a file id.
8///
9/// This delegates to Typst's own [`PathOrStr::resolve`] implementation so
10/// callers keep the same relative-path, absolute-path, normalization, and
11/// escaping semantics as Typst evaluation.
12pub fn resolve_path_from_id(within: FileId, path: &str) -> HintedStrResult<RootedPath> {
13    PathOrStr::Str(path.into()).resolve(within)
14}
15
16#[cfg(test)]
17mod tests {
18    use std::str::FromStr;
19
20    use typst::syntax::package::PackageSpec;
21    use typst::syntax::{FileId, RootedPath, VirtualPath, VirtualRoot};
22
23    use super::resolve_path_from_id;
24
25    fn file_id(root: VirtualRoot, path: &str) -> FileId {
26        RootedPath::new(root, VirtualPath::new(path).expect("valid virtual path")).intern()
27    }
28
29    fn package(spec: &str) -> PackageSpec {
30        PackageSpec::from_str(spec).expect("valid package spec")
31    }
32
33    #[test]
34    fn resolve_path_from_id_uses_parent_directory_for_relative_paths() {
35        let current = file_id(VirtualRoot::Project, "/chapter/main.typ");
36        let resolved = resolve_path_from_id(current, "assets/logo.svg").unwrap();
37
38        assert_eq!(resolved.root(), current.root());
39        assert_eq!(
40            resolved.vpath().get_with_slash(),
41            "/chapter/assets/logo.svg"
42        );
43    }
44
45    #[test]
46    fn resolve_path_from_id_keeps_root_fallback_when_base_has_no_parent() {
47        let current = file_id(VirtualRoot::Project, "/");
48        let resolved = resolve_path_from_id(current, "main.typ").unwrap();
49
50        assert_eq!(resolved.root(), current.root());
51        assert_eq!(resolved.vpath().get_with_slash(), "/main.typ");
52    }
53
54    #[test]
55    fn resolve_path_from_id_resolves_absolute_paths_within_same_root() {
56        let root = VirtualRoot::Package(package("@preview/example:0.1.0"));
57        let current = file_id(root, "/chapter/main.typ");
58        let resolved = resolve_path_from_id(current, "/assets/logo.svg").unwrap();
59
60        assert_eq!(resolved.root(), current.root());
61        assert_eq!(resolved.vpath().get_with_slash(), "/assets/logo.svg");
62    }
63
64    #[test]
65    fn resolve_path_from_id_rejects_paths_that_escape_the_root() {
66        let current = file_id(VirtualRoot::Project, "/main.typ");
67
68        assert!(resolve_path_from_id(current, "../outside.typ").is_err());
69    }
70}