typst_as_lib/
file_resolver.rs

1use ecow::eco_format;
2use std::{
3    borrow::Cow,
4    collections::HashMap,
5    path::{Path, PathBuf},
6};
7use typst::{
8    diag::{FileError, FileResult},
9    foundations::Bytes,
10    syntax::{FileId, Source},
11};
12
13use crate::{
14    cached_file_resolver::{CachedFileResolver, IntoCachedFileResolver},
15    conversions::{IntoBytes, IntoFileId, IntoSource},
16    util::{bytes_to_source, not_found},
17};
18
19// https://github.com/typst/typst/blob/16736feb13eec87eb9ca114deaeb4f7eeb7409d2/crates/typst-kit/src/package.rs#L18
20/// The default packages sub directory within the package and package cache paths.
21pub const DEFAULT_PACKAGES_SUBDIR: &str = "typst/packages";
22
23pub trait FileResolver {
24    fn resolve_binary(&self, id: FileId) -> FileResult<Cow<Bytes>>;
25    fn resolve_source(&self, id: FileId) -> FileResult<Cow<Source>>;
26}
27
28#[derive(Debug, Clone)]
29pub(crate) struct MainSourceFileResolver {
30    main_source: Source,
31}
32
33impl MainSourceFileResolver {
34    pub(crate) fn new(main_source: Source) -> Self {
35        Self { main_source }
36    }
37}
38
39impl FileResolver for MainSourceFileResolver {
40    fn resolve_binary(&self, id: FileId) -> FileResult<Cow<Bytes>> {
41        Err(not_found(id))
42    }
43
44    fn resolve_source(&self, id: FileId) -> FileResult<Cow<Source>> {
45        let Self { main_source } = self;
46        if id == main_source.id() {
47            return Ok(Cow::Borrowed(main_source));
48        }
49        Err(not_found(id))
50    }
51}
52
53#[derive(Debug, Clone)]
54pub struct StaticSourceFileResolver {
55    sources: HashMap<FileId, Source>,
56}
57
58impl StaticSourceFileResolver {
59    pub(crate) fn new<IS, S>(sources: IS) -> Self
60    where
61        IS: IntoIterator<Item = S>,
62        S: IntoSource,
63    {
64        let sources = sources
65            .into_iter()
66            .map(|s| {
67                let s = s.into_source();
68                (s.id(), s)
69            })
70            .collect();
71        Self { sources }
72    }
73}
74
75impl FileResolver for StaticSourceFileResolver {
76    fn resolve_binary(&self, id: FileId) -> FileResult<Cow<Bytes>> {
77        Err(not_found(id))
78    }
79
80    fn resolve_source(&self, id: FileId) -> FileResult<Cow<Source>> {
81        self.sources
82            .get(&id)
83            .map(Cow::Borrowed)
84            .ok_or_else(|| not_found(id))
85    }
86}
87
88#[derive(Debug, Clone)]
89pub struct StaticFileResolver {
90    binaries: HashMap<FileId, Bytes>,
91}
92
93impl StaticFileResolver {
94    pub(crate) fn new<IB, F, B>(binaries: IB) -> Self
95    where
96        IB: IntoIterator<Item = (F, B)>,
97        F: IntoFileId,
98        B: IntoBytes,
99    {
100        let binaries = binaries
101            .into_iter()
102            .map(|(id, b)| (id.into_file_id(), b.into_bytes()))
103            .collect();
104        Self { binaries }
105    }
106}
107
108impl FileResolver for StaticFileResolver {
109    fn resolve_binary(&self, id: FileId) -> FileResult<Cow<Bytes>> {
110        self.binaries
111            .get(&id)
112            .map(Cow::Borrowed)
113            .ok_or_else(|| not_found(id))
114    }
115
116    fn resolve_source(&self, id: FileId) -> FileResult<Cow<Source>> {
117        Err(not_found(id))
118    }
119}
120
121#[derive(Debug, Clone)]
122pub struct FileSystemResolver {
123    root: PathBuf,
124    local_package_root: Option<PathBuf>,
125}
126
127impl FileSystemResolver {
128    pub fn new(root: PathBuf) -> Self {
129        let mut root = root.clone();
130        // trailing slash is necessary for resolve function, which is, what this 'hack' does
131        // https://users.rust-lang.org/t/trailing-in-paths/43166/9
132        root.push("");
133        Self {
134            root,
135            local_package_root: None,
136        }
137    }
138
139    /// Use other path to look for local packages
140    #[deprecated(
141        since = "0.14.1",
142        note = "Use `FileSystemResolver::local_package_root` instead"
143    )]
144    pub fn with_local_package_root(self, path: PathBuf) -> Self {
145        Self {
146            local_package_root: Some(path),
147            ..self
148        }
149    }
150
151    /// Use other path to look for local packages
152    pub fn local_package_root(self, local_package_root: PathBuf) -> Self {
153        Self {
154            local_package_root: Some(local_package_root),
155            ..self
156        }
157    }
158
159    fn resolve_bytes(&self, id: FileId) -> FileResult<Vec<u8>> {
160        let Self {
161            root,
162            local_package_root,
163        } = self;
164        // https://github.com/typst/typst/blob/16736feb13eec87eb9ca114deaeb4f7eeb7409d2/crates/typst-kit/src/package.rs#L102C16-L102C38
165        let dir: Cow<Path> = if let Some(package) = id.package() {
166            let data_dir = if let Some(data_dir) = local_package_root {
167                Cow::Borrowed(data_dir)
168            } else if let Some(data_dir) = dirs::data_dir() {
169                Cow::Owned(data_dir.join(DEFAULT_PACKAGES_SUBDIR))
170            } else {
171                return Err(FileError::Other(Some(eco_format!("No data dir set!"))));
172            };
173            let subdir = Path::new(package.namespace.as_str())
174                .join(package.name.as_str())
175                .join(package.version.to_string());
176            Cow::Owned(data_dir.join(subdir))
177        } else {
178            Cow::Borrowed(root)
179        };
180
181        let path = id
182            .vpath()
183            .resolve(&dir)
184            .ok_or_else(|| FileError::NotFound(dir.to_path_buf()))?;
185        let content = std::fs::read(&path).map_err(|error| FileError::from_io(error, &path))?;
186        Ok(content)
187    }
188}
189
190impl IntoCachedFileResolver for FileSystemResolver {
191    fn into_cached(self) -> CachedFileResolver<Self> {
192        CachedFileResolver::new(self)
193            .with_in_memory_source_cache()
194            .with_in_memory_binary_cache()
195    }
196}
197
198impl FileResolver for FileSystemResolver {
199    fn resolve_binary(&self, id: FileId) -> FileResult<Cow<Bytes>> {
200        let b = self.resolve_bytes(id)?;
201        Ok(Cow::Owned(Bytes::new(b)))
202    }
203
204    fn resolve_source(&self, id: FileId) -> FileResult<Cow<Source>> {
205        let file = self.resolve_bytes(id)?;
206        let source = bytes_to_source(id, &file)?;
207        Ok(Cow::Owned(source))
208    }
209}