typst_as_lib/
file_resolver.rs1use 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
19pub const DEFAULT_PACKAGES_SUBDIR: &str = "typst/packages";
22
23pub trait FileResolver {
25 fn resolve_binary(&self, id: FileId) -> FileResult<Cow<'_, Bytes>>;
27 fn resolve_source(&self, id: FileId) -> FileResult<Cow<'_, Source>>;
29}
30
31#[derive(Debug, Clone)]
32pub(crate) struct MainSourceFileResolver {
33 main_source: Source,
34}
35
36impl MainSourceFileResolver {
37 pub(crate) fn new(main_source: Source) -> Self {
38 Self { main_source }
39 }
40}
41
42impl FileResolver for MainSourceFileResolver {
43 fn resolve_binary(&self, id: FileId) -> FileResult<Cow<'_, Bytes>> {
44 Err(not_found(id))
45 }
46
47 fn resolve_source(&self, id: FileId) -> FileResult<Cow<'_, Source>> {
48 let Self { main_source } = self;
49 if id == main_source.id() {
50 return Ok(Cow::Borrowed(main_source));
51 }
52 Err(not_found(id))
53 }
54}
55
56#[derive(Debug, Clone)]
58pub struct StaticSourceFileResolver {
59 sources: HashMap<FileId, Source>,
60}
61
62impl StaticSourceFileResolver {
63 pub(crate) fn new<IS, S>(sources: IS) -> Self
64 where
65 IS: IntoIterator<Item = S>,
66 S: IntoSource,
67 {
68 let sources = sources
69 .into_iter()
70 .map(|s| {
71 let s = s.into_source();
72 (s.id(), s)
73 })
74 .collect();
75 Self { sources }
76 }
77}
78
79impl FileResolver for StaticSourceFileResolver {
80 fn resolve_binary(&self, id: FileId) -> FileResult<Cow<'_, Bytes>> {
81 Err(not_found(id))
82 }
83
84 fn resolve_source(&self, id: FileId) -> FileResult<Cow<'_, Source>> {
85 self.sources
86 .get(&id)
87 .map(Cow::Borrowed)
88 .ok_or_else(|| not_found(id))
89 }
90}
91
92#[derive(Debug, Clone)]
94pub struct StaticFileResolver {
95 binaries: HashMap<FileId, Bytes>,
96}
97
98impl StaticFileResolver {
99 pub(crate) fn new<IB, F, B>(binaries: IB) -> Self
100 where
101 IB: IntoIterator<Item = (F, B)>,
102 F: IntoFileId,
103 B: IntoBytes,
104 {
105 let binaries = binaries
106 .into_iter()
107 .map(|(id, b)| (id.into_file_id(), b.into_bytes()))
108 .collect();
109 Self { binaries }
110 }
111}
112
113impl FileResolver for StaticFileResolver {
114 fn resolve_binary(&self, id: FileId) -> FileResult<Cow<'_, Bytes>> {
115 self.binaries
116 .get(&id)
117 .map(Cow::Borrowed)
118 .ok_or_else(|| not_found(id))
119 }
120
121 fn resolve_source(&self, id: FileId) -> FileResult<Cow<'_, Source>> {
122 Err(not_found(id))
123 }
124}
125
126#[derive(Debug, Clone)]
130pub struct FileSystemResolver {
131 root: PathBuf,
132 local_package_root: Option<PathBuf>,
133}
134
135impl FileSystemResolver {
136 pub fn new(root: PathBuf) -> Self {
145 let mut root = root.clone();
146 root.push("");
149 Self {
150 root,
151 local_package_root: None,
152 }
153 }
154
155 #[deprecated(
157 since = "0.14.1",
158 note = "Use `FileSystemResolver::local_package_root` instead"
159 )]
160 pub fn with_local_package_root(self, path: PathBuf) -> Self {
161 Self {
162 local_package_root: Some(path),
163 ..self
164 }
165 }
166
167 pub fn local_package_root(self, local_package_root: PathBuf) -> Self {
177 Self {
178 local_package_root: Some(local_package_root),
179 ..self
180 }
181 }
182
183 fn resolve_bytes(&self, id: FileId) -> FileResult<Vec<u8>> {
184 let Self {
185 root,
186 local_package_root,
187 } = self;
188 let dir: Cow<Path> = if let Some(package) = id.package() {
190 let data_dir = if let Some(data_dir) = local_package_root {
191 Cow::Borrowed(data_dir)
192 } else if let Some(data_dir) = dirs::data_dir() {
193 Cow::Owned(data_dir.join(DEFAULT_PACKAGES_SUBDIR))
194 } else {
195 return Err(FileError::Other(Some(eco_format!("No data dir set!"))));
196 };
197 let subdir = Path::new(package.namespace.as_str())
198 .join(package.name.as_str())
199 .join(package.version.to_string());
200 Cow::Owned(data_dir.join(subdir))
201 } else {
202 Cow::Borrowed(root)
203 };
204
205 let path = id
206 .vpath()
207 .resolve(&dir)
208 .ok_or_else(|| FileError::NotFound(dir.to_path_buf()))?;
209 let content = std::fs::read(&path).map_err(|error| FileError::from_io(error, &path))?;
210 Ok(content)
211 }
212}
213
214impl IntoCachedFileResolver for FileSystemResolver {
215 fn into_cached(self) -> CachedFileResolver<Self> {
216 CachedFileResolver::new(self)
217 .with_in_memory_source_cache()
218 .with_in_memory_binary_cache()
219 }
220}
221
222impl FileResolver for FileSystemResolver {
223 fn resolve_binary(&self, id: FileId) -> FileResult<Cow<'_, Bytes>> {
224 let b = self.resolve_bytes(id)?;
225 Ok(Cow::Owned(Bytes::new(b)))
226 }
227
228 fn resolve_source(&self, id: FileId) -> FileResult<Cow<'_, Source>> {
229 let file = self.resolve_bytes(id)?;
230 let source = bytes_to_source(id, &file)?;
231 Ok(Cow::Owned(source))
232 }
233}