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