Skip to main content

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::{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    /// # Errors
40    fn get_document(&self, uri: &DocumentUri) -> Result<Document, BackendError>;
41
42    /// # Errors
43    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    /// # Errors
51    fn get_module(&self, uri: &ModuleUri) -> Result<ModuleLike, BackendError>;
52
53    /// # Errors
54    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    /// # Errors
75    fn get_html_body(&self, d: &DocumentUri) -> Result<(Box<[Css]>, Box<str>), BackendError>;
76
77    /// # Errors
78    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    /// # Errors
88    fn get_html_body_inner(&self, d: &DocumentUri) -> Result<(Box<[Css]>, Box<str>), BackendError>;
89
90    /// # Errors
91    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    /// # Errors
101    fn get_html_full(&self, d: &DocumentUri) -> Result<Box<str>, BackendError>;
102
103    /// # Errors
104    fn get_html_fragment(
105        &self,
106        d: &DocumentUri,
107        range: DocumentRange,
108    ) -> Result<(Box<[Css]>, Box<str>), BackendError>;
109
110    /// # Errors
111    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    /// # Errors
122    fn get_reference<T: bincode::Decode<()>>(&self, rf: &DocDataRef<T>) -> Result<T, BackendError>
123    where
124        Self: Sized;
125
126    /// # Errors
127    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    /// # Errors
147    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    /// # Errors
157    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    /// # Errors
170    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    /// # Errors
183    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    /// # Errors
196    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    /// # Panics
309    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        //GlobalBackend.get_notations::<E>(uri)
568    }
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}