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