1mod global;
2mod sandbox;
3mod temp;
4
5use crate::{
6 Archive, BackendError, BuildableArchive, LocalArchive, MathArchive,
7 artifacts::{Artifact, FileOrString},
8 formats::BuildTargetId,
9 manager::ArchiveOrGroup,
10 utils::{
11 AsyncEngine,
12 errors::ArtifactSaveError,
13 path_ext::{PathExt, RelPath},
14 },
15};
16use ftml_ontology::{
17 domain::{
18 SharedDeclaration,
19 declarations::IsDeclaration,
20 modules::{Module, ModuleLike},
21 },
22 narrative::{
23 DocDataRef, DocumentRange, SharedDocumentElement,
24 documents::Document,
25 elements::{DocumentElement, IsDocumentElement, Notation},
26 },
27 utils::Css,
28};
29use ftml_uris::{
30 ArchiveId, DocumentElementUri, DocumentUri, IsDomainUri, IsNarrativeUri, ModuleUri, NamedUri,
31 SymbolUri, UriPath,
32};
33pub use global::*;
34pub use sandbox::*;
35use std::path::Path;
36pub use temp::*;
37
38pub trait LocalBackend {
39 type ArchiveIter<'a>: IntoIterator<Item = &'a Archive>
40 where
41 Self: Sized;
42
43 fn get_document(&self, uri: &DocumentUri) -> Result<Document, BackendError>;
45
46 fn get_document_async<A: AsyncEngine>(
48 &self,
49 uri: &DocumentUri,
50 ) -> impl Future<Output = Result<Document, BackendError>> + Send + use<Self, A>
51 where
52 Self: Sized;
53
54 fn get_module(&self, uri: &ModuleUri) -> Result<ModuleLike, BackendError>;
56
57 fn get_module_async<A: AsyncEngine>(
59 &self,
60 uri: &ModuleUri,
61 ) -> impl Future<Output = Result<ModuleLike, BackendError>> + Send + use<Self, A>
62 where
63 Self: Sized;
64
65 fn with_archive_or_group<R>(
66 &self,
67 id: &ArchiveId,
68 f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
69 ) -> R
70 where
71 Self: Sized;
72 fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
73 where
74 Self: Sized;
75 fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R
76 where
77 Self: Sized;
78 fn get_html_body(&self, d: &DocumentUri) -> Result<(Box<[Css]>, Box<str>), BackendError>;
80
81 fn get_html_body_async<A: AsyncEngine>(
83 &self,
84 d: &ftml_uris::DocumentUri,
85 ) -> impl Future<Output = Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError>>
86 + Send
87 + use<Self, A>
88 where
89 Self: Sized;
90
91 fn get_html_body_inner(&self, d: &DocumentUri) -> Result<(Box<[Css]>, Box<str>), BackendError>;
93
94 fn get_html_body_inner_async<A: AsyncEngine>(
96 &self,
97 d: &ftml_uris::DocumentUri,
98 ) -> impl Future<Output = Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError>>
99 + Send
100 + use<Self, A>
101 where
102 Self: Sized;
103
104 fn get_html_full(&self, d: &DocumentUri) -> Result<Box<str>, BackendError>;
106
107 fn get_html_fragment(
109 &self,
110 d: &DocumentUri,
111 range: DocumentRange,
112 ) -> Result<(Box<[Css]>, Box<str>), BackendError>;
113
114 fn get_html_fragment_async<A: AsyncEngine>(
116 &self,
117 d: &ftml_uris::DocumentUri,
118 range: ftml_ontology::narrative::DocumentRange,
119 ) -> impl Future<Output = Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError>>
120 + Send
121 + use<Self, A>
122 where
123 Self: Sized;
124
125 fn get_reference<T: bincode::Decode<()>>(&self, rf: &DocDataRef<T>) -> Result<T, BackendError>
127 where
128 Self: Sized;
129
130 fn get_declaration<T: IsDeclaration>(
132 &self,
133 uri: &SymbolUri,
134 ) -> Result<SharedDeclaration<T>, BackendError>
135 where
136 Self: Sized,
137 {
138 if uri.module.name().is_simple() {
139 let m = self.get_module(uri.module_uri())?;
140 return m
141 .get_as(uri.name())
142 .ok_or(BackendError::NotFound(ftml_uris::UriKind::Symbol));
143 }
144 let uri = uri.clone().simple_module();
145 let m = self.get_module(uri.module_uri())?;
146 m.get_as(uri.name())
147 .ok_or(BackendError::NotFound(ftml_uris::UriKind::Symbol))
148 }
149
150 fn save(
152 &self,
153 in_doc: &ftml_uris::DocumentUri,
154 rel_path: Option<&UriPath>,
155 log: FileOrString,
156 from: BuildTargetId,
157 result: Option<Box<dyn Artifact>>,
158 ) -> std::result::Result<(), ArtifactSaveError>;
159
160 fn get_document_element(
162 &self,
163 uri: &DocumentElementUri,
164 ) -> Result<SharedDocumentElement<DocumentElement>, BackendError>
165 where
166 Self: Sized,
167 {
168 let d = self.get_document(uri.document_uri())?;
169 d.get(uri.name())
170 .ok_or(BackendError::NotFound(ftml_uris::UriKind::DocumentElement))
171 }
172
173 async fn get_document_element_async<A: AsyncEngine>(
175 &self,
176 uri: &DocumentElementUri,
177 ) -> Result<SharedDocumentElement<DocumentElement>, BackendError>
178 where
179 Self: Sized,
180 {
181 let d = self.get_document_async::<A>(uri.document_uri()).await?;
182 d.get(uri.name())
183 .ok_or(BackendError::NotFound(ftml_uris::UriKind::DocumentElement))
184 }
185
186 fn get_typed_document_element<T: IsDocumentElement>(
188 &self,
189 uri: &DocumentElementUri,
190 ) -> Result<SharedDocumentElement<T>, BackendError>
191 where
192 Self: Sized,
193 {
194 let d = self.get_document(uri.document_uri())?;
195 d.get_as(uri.name())
196 .ok_or(BackendError::NotFound(ftml_uris::UriKind::DocumentElement))
197 }
198
199 async fn get_typed_document_element_async<A: AsyncEngine, T: IsDocumentElement>(
201 &self,
202 uri: &DocumentElementUri,
203 ) -> Result<SharedDocumentElement<T>, BackendError>
204 where
205 Self: Sized,
206 {
207 let d = self.get_document_async::<A>(uri.document_uri()).await?;
208 d.get_as(uri.name())
209 .ok_or(BackendError::NotFound(ftml_uris::UriKind::DocumentElement))
210 }
211
212 fn uri_of(&self, p: &Path) -> Option<DocumentUri>
213 where
214 Self: Sized,
215 {
216 self.archive_of_source(p, |a, rel_path| {
217 let str = rel_path.as_os_str().to_str()?;
218 DocumentUri::from_archive_relpath(a.uri().clone(), str).ok()
219 })
220 .flatten()
221 }
222
223 fn archive_of_source<R>(
224 &self,
225 p: &Path,
226 mut f: impl FnMut(&LocalArchive, RelPath) -> R,
227 ) -> Option<R>
228 where
229 Self: Sized,
230 {
231 self.with_archives(|archives| {
232 for a in archives {
233 let Archive::Local(a) = a else { continue };
234 if p.relative_to(&a.path()).is_some() {
235 let rel_path = p.relative_to(&a.source_dir())?;
236 return Some(f(a, rel_path));
237 }
238 }
239 None
240 })
241 }
242
243 fn archive_of<R>(&self, p: &Path, mut f: impl FnMut(&LocalArchive, RelPath) -> R) -> Option<R>
244 where
245 Self: Sized,
246 {
247 self.with_archives(|archives| {
248 for a in archives {
249 let Archive::Local(a) = a else { continue };
250 if let Some(rp) = p.relative_to(&a.path()) {
251 return Some(f(a, rp));
252 }
253 }
254 None
255 })
256 }
257
258 fn with_local_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&LocalArchive>) -> R) -> R
259 where
260 Self: Sized,
261 {
262 self.with_archive(id, |a| {
263 f(a.and_then(|a| match a {
264 Archive::Local(a) => Some(&**a),
265 Archive::Ext(..) => None,
266 }))
267 })
268 }
269
270 fn with_buildable_archive<R>(
271 &self,
272 id: &ArchiveId,
273 f: impl FnOnce(Option<&dyn BuildableArchive>) -> R,
274 ) -> R
275 where
276 Self: Sized,
277 {
278 self.with_archive(id, |a| {
279 f(a.and_then(|a| match a {
280 Archive::Local(a) => Some(&**a as _),
281 Archive::Ext(_, e) => e.buildable(),
282 }))
283 })
284 }
285
286 #[cfg(feature = "rdf")]
287 fn get_notations<E: AsyncEngine>(
288 &self,
289 uri: &SymbolUri,
290 ) -> impl Iterator<Item = (DocumentElementUri, Notation)>
291 where
292 Self: Sized;
293
294 #[cfg(feature = "rdf")]
295 fn get_var_notations<E: AsyncEngine>(
296 &self,
297 uri: &DocumentElementUri,
298 ) -> impl Iterator<Item = (DocumentElementUri, Notation)>
299 where
300 Self: Sized;
301}
302
303#[derive(Clone, Debug)]
304pub enum AnyBackend {
305 Global,
306 Temp(TemporaryBackend),
307 Sandbox(SandboxedBackend),
308}
309
310impl AnyBackend {
311 #[must_use]
312 pub fn mathhub(&self) -> &Path {
314 match self {
315 Self::Global | Self::Temp(_) => crate::mathhub::mathhubs()
316 .iter()
317 .next()
318 .expect("No mathhubs found"),
319 Self::Sandbox(sb) => sb.root(),
320 }
321 }
322
323 pub fn mathhubs(&self) -> impl Iterator<Item = &Path> {
324 match self {
325 Self::Global | Self::Temp(_) => {
326 either::Left(crate::mathhub::mathhubs().iter().copied())
327 }
328 Self::Sandbox(sb) => either::Right(
329 std::iter::once(sb.root()).chain(crate::mathhub::mathhubs().iter().copied()),
330 ),
331 }
332 }
333}
334
335impl LocalBackend for AnyBackend {
336 type ArchiveIter<'a> = either::Either<
337 std::slice::Iter<'a, Archive>,
338 <SandboxedBackend as LocalBackend>::ArchiveIter<'a>,
339 >;
340
341 fn save(
342 &self,
343 in_doc: &ftml_uris::DocumentUri,
344 rel_path: Option<&UriPath>,
345 log: FileOrString,
346 from: BuildTargetId,
347 result: Option<Box<dyn Artifact>>,
348 ) -> std::result::Result<(), ArtifactSaveError> {
349 match self {
350 Self::Global => GlobalBackend.save(in_doc, rel_path, log, from, result),
351 Self::Temp(b) => b.save(in_doc, rel_path, log, from, result),
352 Self::Sandbox(b) => b.save(in_doc, rel_path, log, from, result),
353 }
354 }
355
356 fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
357 where
358 Self: Sized,
359 {
360 match self {
361 Self::Global => GlobalBackend.with_archives(|i| f(either::Either::Left(i.iter()))),
362 Self::Temp(b) => b.with_archives(f),
363 Self::Sandbox(b) => b.with_archives(|i| f(either::Either::Right(i))),
364 }
365 }
366
367 fn get_reference<T: bincode::Decode<()>>(
368 &self,
369 rf: &ftml_ontology::narrative::DocDataRef<T>,
370 ) -> Result<T, BackendError>
371 where
372 Self: Sized,
373 {
374 match self {
375 Self::Global => GlobalBackend.get_reference(rf),
376 Self::Temp(b) => b.get_reference(rf),
377 Self::Sandbox(b) => b.get_reference(rf),
378 }
379 }
380
381 fn get_document(&self, uri: &ftml_uris::DocumentUri) -> Result<Document, BackendError> {
382 match self {
383 Self::Global => GlobalBackend.get_document(uri),
384 Self::Temp(b) => b.get_document(uri),
385 Self::Sandbox(b) => b.get_document(uri),
386 }
387 }
388
389 #[allow(clippy::future_not_send)]
390 fn get_document_async<A: AsyncEngine>(
391 &self,
392 uri: &DocumentUri,
393 ) -> impl Future<Output = Result<Document, BackendError>> + Send + use<A>
394 where
395 Self: Sized,
396 {
397 match self {
398 Self::Global => either::Left(GlobalBackend.get_document_async::<A>(uri)),
399 Self::Temp(b) => either::Right(either::Left(b.get_document_async::<A>(uri))),
400 Self::Sandbox(b) => either::Right(either::Right(b.get_document_async::<A>(uri))),
401 }
402 }
403
404 fn get_html_body(
405 &self,
406 d: &ftml_uris::DocumentUri,
407 ) -> Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError> {
408 match self {
409 Self::Global => GlobalBackend.get_html_body(d),
410 Self::Temp(b) => b.get_html_body(d),
411 Self::Sandbox(b) => b.get_html_body(d),
412 }
413 }
414
415 fn get_html_body_async<A: AsyncEngine>(
416 &self,
417 d: &ftml_uris::DocumentUri,
418 ) -> impl Future<Output = Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError>>
419 + Send
420 + use<A>
421 where
422 Self: Sized,
423 {
424 match self {
425 Self::Global => either::Left(GlobalBackend.get_html_body_async::<A>(d)),
426 Self::Temp(b) => either::Right(either::Left(b.get_html_body_async::<A>(d))),
427 Self::Sandbox(b) => either::Right(either::Right(b.get_html_body_async::<A>(d))),
428 }
429 }
430
431 fn get_html_body_inner(
432 &self,
433 d: &ftml_uris::DocumentUri,
434 ) -> Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError> {
435 match self {
436 Self::Global => GlobalBackend.get_html_body_inner(d),
437 Self::Temp(b) => b.get_html_body_inner(d),
438 Self::Sandbox(b) => b.get_html_body_inner(d),
439 }
440 }
441
442 fn get_html_body_inner_async<A: AsyncEngine>(
443 &self,
444 d: &ftml_uris::DocumentUri,
445 ) -> impl Future<Output = Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError>>
446 + use<A>
447 + Send
448 where
449 Self: Sized,
450 {
451 match self {
452 Self::Global => either::Left(GlobalBackend.get_html_body_inner_async::<A>(d)),
453 Self::Temp(b) => either::Right(either::Left(b.get_html_body_inner_async::<A>(d))),
454 Self::Sandbox(b) => either::Right(either::Right(b.get_html_body_inner_async::<A>(d))),
455 }
456 }
457
458 fn get_html_full(&self, d: &ftml_uris::DocumentUri) -> Result<Box<str>, BackendError> {
459 match self {
460 Self::Global => GlobalBackend.get_html_full(d),
461 Self::Temp(b) => b.get_html_full(d),
462 Self::Sandbox(b) => b.get_html_full(d),
463 }
464 }
465
466 fn get_html_fragment(
467 &self,
468 d: &ftml_uris::DocumentUri,
469 range: ftml_ontology::narrative::DocumentRange,
470 ) -> Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError> {
471 match self {
472 Self::Global => GlobalBackend.get_html_fragment(d, range),
473 Self::Temp(b) => b.get_html_fragment(d, range),
474 Self::Sandbox(b) => b.get_html_fragment(d, range),
475 }
476 }
477
478 fn get_html_fragment_async<A: AsyncEngine>(
479 &self,
480 d: &ftml_uris::DocumentUri,
481 range: ftml_ontology::narrative::DocumentRange,
482 ) -> impl Future<Output = Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError>>
483 + Send
484 + use<A> {
485 match self {
486 Self::Global => either::Left(GlobalBackend.get_html_fragment_async::<A>(d, range)),
487 Self::Temp(b) => either::Right(either::Left(b.get_html_fragment_async::<A>(d, range))),
488 Self::Sandbox(b) => {
489 either::Right(either::Right(b.get_html_fragment_async::<A>(d, range)))
490 }
491 }
492 }
493
494 fn get_module(&self, uri: &ftml_uris::ModuleUri) -> Result<ModuleLike, BackendError> {
495 match self {
496 Self::Global => GlobalBackend.get_module(uri),
497 Self::Temp(b) => b.get_module(uri),
498 Self::Sandbox(b) => b.get_module(uri),
499 }
500 }
501
502 fn get_module_async<A: AsyncEngine>(
503 &self,
504 uri: &ModuleUri,
505 ) -> impl Future<Output = Result<ModuleLike, BackendError>> + Send + use<A>
506 where
507 Self: Sized,
508 {
509 match self {
510 Self::Global => either::Left(GlobalBackend.get_module_async::<A>(uri)),
511 Self::Temp(b) => either::Right(either::Left(b.get_module_async::<A>(uri))),
512 Self::Sandbox(b) => either::Right(either::Right(b.get_module_async::<A>(uri))),
513 }
514 }
515
516 fn with_archive_or_group<R>(
517 &self,
518 id: &ftml_uris::ArchiveId,
519 f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
520 ) -> R
521 where
522 Self: Sized,
523 {
524 match self {
525 Self::Global => GlobalBackend.with_archive_or_group(id, f),
526 Self::Temp(b) => b.with_archive_or_group(id, f),
527 Self::Sandbox(b) => b.with_archive_or_group(id, f),
528 }
529 }
530
531 fn with_archive<R>(&self, id: &ftml_uris::ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R
532 where
533 Self: Sized,
534 {
535 match self {
536 Self::Global => GlobalBackend.with_archive(id, f),
537 Self::Temp(b) => b.with_archive(id, f),
538 Self::Sandbox(b) => b.with_archive(id, f),
539 }
540 }
541
542 #[cfg(feature = "rdf")]
543 #[inline]
544 fn get_notations<E: AsyncEngine>(
545 &self,
546 uri: &ftml_uris::SymbolUri,
547 ) -> impl Iterator<
548 Item = (
549 ftml_uris::DocumentElementUri,
550 ftml_ontology::narrative::elements::Notation,
551 ),
552 >
553 where
554 Self: Sized,
555 {
556 GlobalBackend.get_notations::<E>(uri)
557 }
558
559 #[cfg(feature = "rdf")]
560 #[inline]
561 fn get_var_notations<E: AsyncEngine>(
562 &self,
563 uri: &ftml_uris::DocumentElementUri,
564 ) -> impl Iterator<
565 Item = (
566 ftml_uris::DocumentElementUri,
567 ftml_ontology::narrative::elements::Notation,
568 ),
569 >
570 where
571 Self: Sized,
572 {
573 GlobalBackend.get_var_notations::<E>(uri)
574 }
575}