flams_math_archives/backend/
mod.rs

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    /// # Errors
44    fn get_document(&self, uri: &DocumentUri) -> Result<Document, BackendError>;
45
46    /// # Errors
47    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    /// # Errors
55    fn get_module(&self, uri: &ModuleUri) -> Result<ModuleLike, BackendError>;
56
57    /// # Errors
58    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    /// # Errors
79    fn get_html_body(&self, d: &DocumentUri) -> Result<(Box<[Css]>, Box<str>), BackendError>;
80
81    /// # Errors
82    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    /// # Errors
92    fn get_html_body_inner(&self, d: &DocumentUri) -> Result<(Box<[Css]>, Box<str>), BackendError>;
93
94    /// # Errors
95    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    /// # Errors
105    fn get_html_full(&self, d: &DocumentUri) -> Result<Box<str>, BackendError>;
106
107    /// # Errors
108    fn get_html_fragment(
109        &self,
110        d: &DocumentUri,
111        range: DocumentRange,
112    ) -> Result<(Box<[Css]>, Box<str>), BackendError>;
113
114    /// # Errors
115    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    /// # Errors
126    fn get_reference<T: bincode::Decode<()>>(&self, rf: &DocDataRef<T>) -> Result<T, BackendError>
127    where
128        Self: Sized;
129
130    /// # Errors
131    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    /// # Errors
151    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    /// # Errors
161    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    /// # Errors
174    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    /// # Errors
187    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    /// # Errors
200    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    /// # Panics
313    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}