1pub mod archives;
2mod cache;
3pub mod docfile;
4pub mod rdf;
5
6use crate::{
7 formats::{HTMLData, SourceFormatId},
8 settings::Settings,
9};
10use archives::{
11 manager::ArchiveManager, source_files::FileState, Archive, ArchiveGroup, ArchiveOrGroup,
12 ArchiveTree, LocalArchive,
13};
14use cache::BackendCache;
15use eyre::Context;
16use flams_ontology::{
17 content::{
18 checking::ModuleChecker,
19 declarations::{Declaration, DeclarationTrait, OpenDeclaration},
20 modules::Module,
21 terms::Term,
22 ContentReference, ModuleLike,
23 },
24 languages::Language,
25 narration::{
26 checking::DocumentChecker,
27 documents::Document,
28 notations::{Notation, PresentationError, Presenter},
29 paragraphs::LogicalParagraph,
30 problems::Problem,
31 sections::Section,
32 DocumentElement, LazyDocRef, NarrationTrait, NarrativeReference,
33 },
34 uris::{
35 ArchiveId, ArchiveURI, ArchiveURITrait, BaseURI, ContentURITrait, DocumentElementURI,
36 DocumentURI, ModuleURI, NameStep, PathURIRef, PathURITrait, SymbolURI, URIOrRefTrait,
37 URIRefTrait, URIWithLanguage,
38 },
39 Checked, DocumentRange, LocalBackend, Unchecked,
40};
41use flams_utils::{
42 prelude::{HMap, TreeLike},
43 triomphe, unwrap,
44 vecmap::{VecMap, VecSet},
45 PathExt, CSS,
46};
47use lazy_static::lazy_static;
48use parking_lot::RwLock;
49use rdf::RDFStore;
50use std::{
51 ops::Deref,
52 path::{Path, PathBuf},
53 rc::Rc,
54};
55
56#[derive(Clone, Debug)]
57pub enum BackendChange {
58 NewArchive(ArchiveURI),
59 ArchiveUpdate(ArchiveURI),
60 ArchiveDeleted(ArchiveURI),
61 FileChange {
62 archive: ArchiveURI,
63 relative_path: String,
64 format: SourceFormatId,
65 old: Option<FileState>,
66 new: FileState,
67 },
68}
69
70pub trait Backend {
71 type ArchiveIter<'a>: Iterator<Item = &'a Archive>
72 where
73 Self: Sized;
74
75 #[inline]
76 fn presenter(&self) -> StringPresenter<'_, Self>
77 where
78 Self: Sized,
79 {
80 StringPresenter::new(self, false)
81 }
82
83 fn to_any(&self) -> AnyBackend;
84 fn get_document(&self, uri: &DocumentURI) -> Option<Document>;
85 fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike>;
86 fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf>;
87 fn get_declaration<T: DeclarationTrait>(&self, uri: &SymbolURI) -> Option<ContentReference<T>>
88 where
89 Self: Sized,
90 {
91 let m = self.get_module(uri.module())?;
92 ContentReference::new(&m, uri.name())
94 }
95 fn get_document_element<T: NarrationTrait>(
96 &self,
97 uri: &DocumentElementURI,
98 ) -> Option<NarrativeReference<T>>
99 where
100 Self: Sized,
101 {
102 let d = self.get_document(uri.document())?;
103 NarrativeReference::new(&d, uri.name())
105 }
106 fn with_archive_or_group<R>(
107 &self,
108 id: &ArchiveId,
109 f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
110 ) -> R
111 where
112 Self: Sized;
113
114 fn uri_of(&self, p: &Path) -> Option<DocumentURI>
115 where
116 Self: Sized,
117 {
118 #[cfg(windows)]
119 const PREFIX: &str = "\\source\\";
120 #[cfg(not(windows))]
121 const PREFIX: &str = "/source/";
122 self.archive_of(p, |a: &LocalArchive, rp: &str| {
123 DocumentURI::from_archive_relpath(a.uri().owned(), rp.strip_prefix(PREFIX)?).ok()
124 })
125 .flatten()
126 }
127
128 #[allow(irrefutable_let_patterns)]
129 fn archive_of<R>(&self, p: &Path, mut f: impl FnMut(&LocalArchive, &str) -> R) -> Option<R>
130 where
131 Self: Sized,
132 {
133 fn strip<'a>(base: &'a str, ap: &str) -> Option<&'a str> {
134 #[cfg(windows)]
135 {
136 if base.is_empty()
137 || ap.is_empty()
138 || !(&base[0..1]).eq_ignore_ascii_case(&ap[0..1])
139 {
140 return None;
141 }
142 (&base[1..]).strip_prefix(&ap[1..])
143 }
144 #[cfg(not(windows))]
145 {
146 base.strip_prefix(ap)
147 }
148 }
149 let base = p.as_os_str().to_str()?;
150 self.with_archives(|mut a| {
151 a.find_map(|a| {
152 let Archive::Local(a) = a else { return None };
153 let ap = a.path().as_os_str().to_str()?;
154 if let Some(r) = strip(base, ap) {
155 if r.starts_with(std::path::PathBuf::PATH_SEPARATOR) || r.is_empty() {
156 return Some(f(a, r));
157 }
158 }
159 None
160 })
161 })
162 }
163
164 fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
165 where
166 Self: Sized;
167
168 fn submit_triples(
171 &self,
172 in_doc: &DocumentURI,
173 rel_path: &str,
174 iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
175 ) where
176 Self: Sized;
177
178 fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R
179 where
180 Self: Sized;
181
182 fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)>;
183
184 fn get_html_full(&self, d: &DocumentURI) -> Option<String>;
185
186 fn get_html_fragment(
187 &self,
188 d: &DocumentURI,
189 range: DocumentRange,
190 ) -> Option<(Vec<CSS>, String)>;
191
192 fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T>
193 where
194 Self: Sized;
195
196 #[allow(unreachable_patterns)]
197 fn with_local_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&LocalArchive>) -> R) -> R
198 where
199 Self: Sized,
200 {
201 self.with_archive(id, |a| {
202 f(a.and_then(|a| match a {
203 Archive::Local(a) => Some(a),
204 _ => None,
205 }))
206 })
207 }
208
209 fn get_notations(&self, uri: &SymbolURI) -> Option<VecSet<(DocumentElementURI, Notation)>>
210 where
211 Self: Sized,
212 {
213 use flams_ontology::rdf::ontologies::ulo2;
214 use rdf::sparql::{Select, Var};
215 let iri = uri.to_iri();
216 let q = Select {
217 subject: Var('n'),
218 pred: ulo2::NOTATION_FOR.into_owned(),
219 object: iri,
220 };
221 let ret: VecSet<_> = GlobalBackend::get()
222 .triple_store()
223 .query(q.into())
224 .ok()?
225 .into_uris()
226 .filter_map(|uri| {
227 let elem = self.get_document_element::<DocumentElement<Checked>>(&uri)?;
228 let DocumentElement::Notation { notation, .. } = elem.as_ref() else {
229 return None;
230 };
231 self.get_reference(notation).ok().map(|n| (uri, n))
232 })
233 .collect();
234 if ret.is_empty() {
235 None
236 } else {
237 Some(ret)
238 }
239 }
240
241 fn get_var_notations(
242 &self,
243 uri: &DocumentElementURI,
244 ) -> Option<VecSet<(DocumentElementURI, Notation)>>
245 where
246 Self: Sized,
247 {
248 let parent = uri.parent();
249 let parent = self.get_document_element::<DocumentElement<Checked>>(&parent)?;
250 let mut ch = parent.as_ref().children().iter();
251 let mut stack = Vec::new();
252 let mut ret = VecSet::new();
253 loop {
254 let Some(next) = ch.next() else {
255 if let Some(n) = stack.pop() {
256 ch = n;
257 continue;
258 }
259 break;
260 };
261 let (uri, not) = match next {
262 DocumentElement::Module { children, .. }
263 | DocumentElement::Section(Section { children, .. })
264 | DocumentElement::Morphism { children, .. }
265 | DocumentElement::MathStructure { children, .. }
266 | DocumentElement::Extension { children, .. }
267 | DocumentElement::Paragraph(LogicalParagraph { children, .. })
268 | DocumentElement::Problem(Problem { children, .. }) => {
269 let old = std::mem::replace(&mut ch, children.iter());
270 stack.push(old);
271 continue;
272 }
273 DocumentElement::VariableNotation {
274 variable,
275 id,
276 notation,
277 } if variable == uri => (id, notation),
278 _ => continue,
279 };
280 let Some(r) = self.get_reference(not).ok() else {
281 continue;
282 };
283 ret.insert((uri.clone(), r));
284 }
285 if ret.is_empty() {
286 None
287 } else {
288 Some(ret)
289 }
290 }
291
292 #[inline]
297 fn as_checker(&self) -> AsChecker<Self>
298 where
299 Self: Sized,
300 {
301 AsChecker(self)
302 }
303}
304
305#[derive(Clone, Debug)]
306pub enum AnyBackend {
307 Global(&'static GlobalBackend),
308 Temp(TemporaryBackend),
309 Sandbox(SandboxedBackend),
310}
311impl AnyBackend {
312 #[must_use]
313 pub fn mathhubs(&self) -> Vec<PathBuf> {
314 let mut global: Vec<PathBuf> = Settings::get()
315 .mathhubs
316 .iter()
317 .map(|p| p.to_path_buf())
318 .collect();
319 match self {
320 Self::Global(_) | Self::Temp(_) => global,
321 Self::Sandbox(s) => {
322 global.insert(0, s.0.path.to_path_buf());
323 global
324 }
325 }
326 }
327}
328
329pub enum EitherArchiveIter<'a> {
330 Global(std::slice::Iter<'a, Archive>),
331 Sandbox(std::iter::Chain<std::slice::Iter<'a, Archive>, std::slice::Iter<'a, Archive>>),
332}
333impl<'a> From<std::slice::Iter<'a, Archive>> for EitherArchiveIter<'a> {
334 #[inline]
335 fn from(value: std::slice::Iter<'a, Archive>) -> Self {
336 Self::Global(value)
337 }
338}
339impl<'a> From<std::iter::Chain<std::slice::Iter<'a, Archive>, std::slice::Iter<'a, Archive>>>
340 for EitherArchiveIter<'a>
341{
342 #[inline]
343 fn from(
344 value: std::iter::Chain<std::slice::Iter<'a, Archive>, std::slice::Iter<'a, Archive>>,
345 ) -> Self {
346 Self::Sandbox(value)
347 }
348}
349impl<'a> Iterator for EitherArchiveIter<'a> {
350 type Item = &'a Archive;
351 #[inline]
352 fn next(&mut self) -> Option<Self::Item> {
353 match self {
354 Self::Global(i) => i.next(),
355 Self::Sandbox(i) => i.next(),
356 }
357 }
358}
359
360impl Backend for AnyBackend {
361 type ArchiveIter<'a> = EitherArchiveIter<'a>;
362 #[inline]
363 fn to_any(&self) -> AnyBackend {
364 self.clone()
365 }
366
367 #[inline]
368 fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
369 where
370 Self: Sized,
371 {
372 match self {
373 Self::Global(b) => b.with_archives(|i| f(i.into())),
374 Self::Temp(b) => b.with_archives(f),
375 Self::Sandbox(b) => b.with_archives(|i| f(i.into())),
376 }
377 }
378
379 #[inline]
380 fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T> {
381 match self {
382 Self::Global(b) => b.get_reference(rf),
383 Self::Temp(b) => b.get_reference(rf),
384 Self::Sandbox(b) => b.get_reference(rf),
385 }
386 }
387
388 #[inline]
389 fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)> {
390 match self {
391 Self::Global(b) => b.get_html_body(d, full),
392 Self::Temp(b) => b.get_html_body(d, full),
393 Self::Sandbox(b) => b.get_html_body(d, full),
394 }
395 }
396
397 #[inline]
398 fn get_html_full(&self, d: &DocumentURI) -> Option<String> {
399 match self {
400 Self::Global(b) => b.get_html_full(d),
401 Self::Temp(b) => b.get_html_full(d),
402 Self::Sandbox(b) => b.get_html_full(d),
403 }
404 }
405
406 #[inline]
407 fn get_html_fragment(
408 &self,
409 d: &DocumentURI,
410 range: DocumentRange,
411 ) -> Option<(Vec<CSS>, String)> {
412 match self {
413 Self::Global(b) => b.get_html_fragment(d, range),
414 Self::Temp(b) => b.get_html_fragment(d, range),
415 Self::Sandbox(b) => b.get_html_fragment(d, range),
416 }
417 }
418
419 #[inline]
420 fn submit_triples(
421 &self,
422 in_doc: &DocumentURI,
423 rel_path: &str,
424 iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
425 ) {
426 match self {
427 Self::Global(b) => b.submit_triples(in_doc, rel_path, iter),
428 Self::Temp(b) => b.submit_triples(in_doc, rel_path, iter),
429 Self::Sandbox(b) => b.submit_triples(in_doc, rel_path, iter),
430 }
431 }
432
433 #[inline]
434 fn get_document(&self, uri: &DocumentURI) -> Option<Document> {
435 match self {
436 Self::Global(b) => b.get_document(uri),
437 Self::Temp(b) => b.get_document(uri),
438 Self::Sandbox(b) => b.get_document(uri),
439 }
440 }
441
442 #[inline]
443 fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike> {
444 match self {
445 Self::Global(b) => b.get_module(uri),
446 Self::Temp(b) => b.get_module(uri),
447 Self::Sandbox(b) => b.get_module(uri),
448 }
449 }
450
451 #[inline]
452 fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf> {
453 match self {
454 Self::Global(b) => b.get_base_path(id),
455 Self::Temp(b) => b.get_base_path(id),
456 Self::Sandbox(b) => b.get_base_path(id),
457 }
458 }
459
460 #[inline]
461 fn get_declaration<T: DeclarationTrait>(&self, uri: &SymbolURI) -> Option<ContentReference<T>>
462 where
463 Self: Sized,
464 {
465 match self {
466 Self::Global(b) => b.get_declaration(uri),
467 Self::Temp(b) => b.get_declaration(uri),
468 Self::Sandbox(b) => b.get_declaration(uri),
469 }
470 }
471
472 #[inline]
473 fn with_archive_or_group<R>(
474 &self,
475 id: &ArchiveId,
476 f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
477 ) -> R
478 where
479 Self: Sized,
480 {
481 match self {
482 Self::Global(b) => b.with_archive_or_group(id, f),
483 Self::Temp(b) => b.with_archive_or_group(id, f),
484 Self::Sandbox(b) => b.with_archive_or_group(id, f),
485 }
486 }
487
488 #[inline]
489 fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R
490 where
491 Self: Sized,
492 {
493 match self {
494 Self::Global(b) => b.with_archive(id, f),
495 Self::Temp(b) => b.with_archive(id, f),
496 Self::Sandbox(b) => b.with_archive(id, f),
497 }
498 }
499
500 #[inline]
501 fn with_local_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&LocalArchive>) -> R) -> R
502 where
503 Self: Sized,
504 {
505 match self {
506 Self::Global(b) => b.with_local_archive(id, f),
507 Self::Temp(b) => b.with_local_archive(id, f),
508 Self::Sandbox(b) => b.with_local_archive(id, f),
509 }
510 }
511}
512
513#[derive(Debug)]
514pub struct GlobalBackend {
515 archives: ArchiveManager,
516 cache: RwLock<cache::BackendCache>,
517 triple_store: RDFStore,
518}
519
520lazy_static! {
521 static ref GLOBAL: GlobalBackend = GlobalBackend {
522 archives: ArchiveManager::default(),
523 cache: RwLock::new(cache::BackendCache::default()),
524 triple_store: RDFStore::default()
525 };
526}
527
528impl GlobalBackend {
529 #[inline]
530 #[must_use]
531 pub fn get() -> &'static Self
532 where
533 Self: Sized,
534 {
535 &GLOBAL
536 }
537
538 pub fn initialize() {
539 let settings = crate::settings::Settings::get();
540 let archives = Self::get().manager();
541 for p in settings.mathhubs.iter().rev() {
542 archives.load(p);
543 }
544 let f = || {
545 let backend = Self::get();
546 backend
547 .triple_store()
548 .load_archives(&backend.all_archives());
549 };
550 #[cfg(feature = "tokio")]
551 flams_utils::background(f);
552 #[cfg(not(feature = "tokio"))]
553 f();
554 #[cfg(feature = "tantivy")]
555 {
556 #[cfg(feature = "tokio")]
557 flams_utils::background(|| crate::search::Searcher::get().reload());
558 #[cfg(not(feature = "tokio"))]
559 crate::search::Searcher::get().reload();
560 }
561 }
562
563 pub fn new_archive(
564 &self,
565 id: &ArchiveId,
566 base_uri: &BaseURI,
567 format: &str,
568 default_file: &str,
569 content: &str,
570 ) -> Result<PathBuf, eyre::Report> {
571 let settings = crate::settings::Settings::get();
572 let mh = settings
574 .mathhubs
575 .first()
576 .expect("No mathhub directories found!");
577 let meta_inf = id
578 .steps()
579 .fold(mh.to_path_buf(), |p, s| p.join(s))
580 .join("META-INF");
581
582 std::fs::create_dir_all(&meta_inf)
583 .wrap_err_with(|| format!("Failed to create directory {}", meta_inf.display()))?;
584 std::fs::write(
585 meta_inf.join("MANIFEST.MF"),
586 &format!("id: {id}\nurl-base: {base_uri}\nformat: {format}"),
587 )
588 .wrap_err_with(|| format!("Failed to create file {}/MANIFEST.MF", meta_inf.display()))?;
589
590 let parent = unwrap!(meta_inf.parent());
591 std::fs::write(
592 parent.join(".gitignore"),
593 include_str!("gitignore_template.txt"),
594 )
595 .wrap_err_with(|| format!("Failed to create file {}/.gitignore", parent.display()))?;
596
597 let lib = parent.join("lib");
598 std::fs::create_dir_all(&lib)
599 .wrap_err_with(|| format!("Failed to create directory {}", lib.display()))?;
600 std::fs::write(
601 lib.join("preamble.tex"),
602 &format!("% preamble code for {id}"),
603 )
604 .wrap_err_with(|| format!("Failed to create file {}/preamble.tex", lib.display()))?;
605
606 let source = parent.join("source");
607 std::fs::create_dir_all(&source)
608 .wrap_err_with(|| format!("Failed to create directory {}", source.display()))?;
609 let dflt = source.join(default_file);
610 std::fs::write(&dflt, content)
611 .wrap_err_with(|| format!("Failed to create file {}/{default_file}", lib.display()))?;
612 self.manager().load(parent);
613
614 Ok(dflt)
615 }
616
617 pub fn artifact_path(&self, uri: &DocumentURI, format: &str) -> Option<PathBuf> {
618 let id = uri.archive_id();
619 let language = uri.language();
620 let name = uri.name().first_name();
621 self.with_local_archive(id, |a| {
622 a.and_then(|a| a.get_filepath(uri.path(), name, language, format))
623 })
624 }
625
626 #[inline]
627 pub fn with_archive_tree<R>(&self, f: impl FnOnce(&ArchiveTree) -> R) -> R {
628 self.archives.with_tree(f)
629 }
630
631 pub fn reset(&self) {
632 self.cache.write().clear();
633 self.archives.reinit(
634 |_| (),
635 crate::settings::Settings::get()
636 .mathhubs
637 .iter()
638 .map(|b| &**b),
639 );
640 self.triple_store.clear();
641 let f = || {
642 let global = Self::get();
643 global.triple_store.load_archives(&global.all_archives());
644 };
645 #[cfg(feature = "tokio")]
646 flams_utils::background(f);
647 #[cfg(not(feature = "tokio"))]
648 f();
649 #[cfg(feature = "tantivy")]
650 {
651 #[cfg(feature = "tokio")]
652 flams_utils::background(|| crate::search::Searcher::get().reload());
653 #[cfg(not(feature = "tokio"))]
654 crate::search::Searcher::get().reload();
655 }
656 }
657
658 #[cfg(feature = "tokio")]
659 pub async fn get_html_body_async(
660 &self,
661 d: &DocumentURI,
662 full: bool,
663 ) -> Option<(Vec<CSS>, String)> {
664 let f = self.manager().with_archive(d.archive_id(), move |a| {
665 a.map(move |a| {
666 a.load_html_body_async(d.path(), d.name().first_name(), d.language(), full)
667 })
668 })??;
669 f.await
670 }
671
672 #[cfg(feature = "tokio")]
673 pub async fn get_html_full_async(&self, d: &DocumentURI) -> Option<String> {
674 let f = self.manager().with_archive(d.archive_id(), move |a| {
675 a.map(move |a| a.load_html_full_async(d.path(), d.name().first_name(), d.language()))
676 })??;
677 f.await
678 }
679
680 #[cfg(feature = "tokio")]
681 pub async fn get_html_fragment_async(
682 &self,
683 d: &DocumentURI,
684 range: DocumentRange,
685 ) -> Option<(Vec<CSS>, String)> {
686 let f = self.manager().with_archive(d.archive_id(), move |a| {
687 a.map(move |a| {
688 a.load_html_fragment_async(d.path(), d.name().first_name(), d.language(), range)
689 })
690 })??;
691 f.await
692 }
693
694 #[inline]
695 pub const fn manager(&self) -> &ArchiveManager {
696 &self.archives
697 }
698
699 #[inline]
700 pub const fn triple_store(&self) -> &RDFStore {
701 &self.triple_store
702 }
703
704 #[inline]
705 pub fn all_archives(&self) -> impl Deref<Target = [Archive]> + '_ {
706 self.archives.all_archives()
707 }
708
709 #[cfg(feature = "tokio")]
710 #[allow(clippy::similar_names)]
711 #[allow(clippy::significant_drop_tightening)]
712 pub async fn get_document_async(&self, uri: &DocumentURI) -> Option<Document> {
713 {
714 let lock = self.cache.read();
715 if let Some(doc) = lock.has_document(uri) {
716 return Some(doc.clone());
717 }
718 }
719 let uri = uri.clone();
720 tokio::task::spawn_blocking(move || {
721 let slf = Self::get();
722 let mut cache = slf.cache.write();
723 let mut flattener = GlobalFlattener(&mut cache, &slf.archives);
724 flattener.load_document(uri.as_path(), uri.language(), uri.name().first_name())
725 })
726 .await
727 .ok()
728 .flatten()
729 }
730
731 #[cfg(feature = "tokio")]
732 #[allow(clippy::similar_names)]
733 #[allow(clippy::significant_drop_tightening)]
734 pub async fn get_module_async(&self, uri: &ModuleURI) -> Option<ModuleLike> {
735 {
736 let lock = self.cache.read();
737 if uri.name().is_simple() {
738 if let Some(m) = lock.has_module(uri) {
739 return Some(ModuleLike::Module(m.clone()));
740 }
741 } else {
742 let top_uri = !uri.clone();
743 if let Some(m) = lock.has_module(&top_uri) {
744 return ModuleLike::in_module(m, uri.name());
745 }
746 }
747 }
748
749 let top = !uri.clone();
750 let m = tokio::task::spawn_blocking(move || {
751 let slf = Self::get();
752 let mut cache = slf.cache.write();
753 let mut flattener = GlobalFlattener(&mut cache, &slf.archives);
754 flattener.load_module(top.as_path(), top.name().first_name())
755 })
756 .await
757 .ok()??;
758 ModuleLike::in_module(&m, uri.name())
759 }
760
761 #[cfg(feature = "tokio")]
762 pub async fn get_declaration_async<T: DeclarationTrait>(
763 &self,
764 uri: &SymbolURI,
765 ) -> Option<ContentReference<T>> {
766 let m = self.get_module_async(uri.module()).await?;
767 ContentReference::new(&m, uri.name())
769 }
770
771 #[cfg(feature = "tokio")]
772 pub async fn get_document_element_async<T: NarrationTrait>(
773 &self,
774 uri: &DocumentElementURI,
775 ) -> Option<NarrativeReference<T>> {
776 let d = self.get_document_async(uri.document()).await?;
777 NarrativeReference::new(&d, uri.name())
779 }
780}
781
782impl Backend for &'static GlobalBackend {
783 type ArchiveIter<'a> = std::slice::Iter<'a, Archive>;
784
785 #[inline]
786 fn to_any(&self) -> AnyBackend {
787 AnyBackend::Global(self)
788 }
789
790 #[inline]
791 fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)> {
792 GlobalBackend::get_html_body(self, d, full)
793 }
794
795 #[inline]
796 fn get_html_full(&self, d: &DocumentURI) -> Option<String> {
797 GlobalBackend::get_html_full(self, d)
798 }
799
800 #[inline]
801 fn get_html_fragment(
802 &self,
803 d: &DocumentURI,
804 range: DocumentRange,
805 ) -> Option<(Vec<CSS>, String)> {
806 GlobalBackend::get_html_fragment(self, d, range)
807 }
808
809 #[inline]
810 fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T> {
811 GlobalBackend::get_reference(self, rf)
812 }
813
814 #[inline]
815 fn submit_triples(
816 &self,
817 in_doc: &DocumentURI,
818 rel_path: &str,
819 iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
820 ) {
821 GlobalBackend::submit_triples(self, in_doc, rel_path, iter);
822 }
823
824 fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
825 where
826 Self: Sized,
827 {
828 GlobalBackend::with_archives(self, f)
829 }
830
831 #[inline]
832 fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R {
833 GlobalBackend::with_archive(self, id, f)
834 }
835
836 #[inline]
837 fn with_local_archive<R>(
838 &self,
839 id: &ArchiveId,
840 f: impl FnOnce(Option<&LocalArchive>) -> R,
841 ) -> R {
842 GlobalBackend::with_local_archive(self, id, f)
843 }
844 #[inline]
845 fn with_archive_or_group<R>(
846 &self,
847 id: &ArchiveId,
848 f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
849 ) -> R {
850 GlobalBackend::with_archive_or_group(self, id, f)
851 }
852 #[inline]
853 fn get_document(&self, uri: &DocumentURI) -> Option<Document> {
854 GlobalBackend::get_document(self, uri)
855 }
856 #[inline]
857 fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike> {
858 GlobalBackend::get_module(self, uri)
859 }
860 #[inline]
861 fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf> {
862 GlobalBackend::get_base_path(self, id)
863 }
864 #[inline]
865 fn get_declaration<T: DeclarationTrait>(&self, uri: &SymbolURI) -> Option<ContentReference<T>> {
866 GlobalBackend::get_declaration(self, uri)
867 }
868}
869
870impl Backend for GlobalBackend {
871 type ArchiveIter<'a> = std::slice::Iter<'a, Archive>;
872
873 #[inline]
874 fn to_any(&self) -> AnyBackend {
875 AnyBackend::Global(Self::get())
876 }
877
878 fn get_html_fragment(
879 &self,
880 d: &DocumentURI,
881 range: DocumentRange,
882 ) -> Option<(Vec<CSS>, String)> {
883 self.archives.with_archive(d.archive_id(), |a| {
884 a.and_then(|a| {
885 a.load_html_fragment(d.path(), d.name().first_name(), d.language(), range)
886 })
887 })
888 }
889
890 fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T> {
891 self.archives.with_archive(rf.in_doc.archive_id(), |a| {
892 let Some(a) = a else {
893 return Err(eyre::eyre!("Archive {} not found", rf.in_doc.archive_id()));
894 };
895 a.load_reference(
896 rf.in_doc.path(),
897 rf.in_doc.name().first_name(),
898 rf.in_doc.language(),
899 DocumentRange {
900 start: rf.start,
901 end: rf.end,
902 },
903 )
904 })
905 }
906
907 #[inline]
908 fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
909 where
910 Self: Sized,
911 {
912 self.archives.with_tree(|t| f(t.archives.iter()))
913 }
914
915 fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)> {
916 self.archives.with_archive(d.archive_id(), |a| {
917 a.and_then(|a| a.load_html_body(d.path(), d.name().first_name(), d.language(), full))
918 })
919 }
920
921 #[inline]
922 fn get_html_full(&self, d: &DocumentURI) -> Option<String> {
923 self.archives.with_archive(d.archive_id(), |a| {
924 a.and_then(|a| a.load_html_full(d.path(), d.name().first_name(), d.language()))
925 })
926 }
927
928 fn submit_triples(
929 &self,
930 in_doc: &DocumentURI,
931 rel_path: &str,
932 iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
933 ) {
934 self.archives.with_archive(in_doc.archive_id(), |a| {
935 if let Some(a) = a {
936 a.submit_triples(in_doc, rel_path, self.triple_store(), true, iter);
937 }
938 });
939 }
940
941 #[inline]
942 fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R {
943 let archives = &*self.all_archives();
944 f(archives.iter().find(|a| a.uri().archive_id() == id))
945 }
946
947 fn with_archive_or_group<R>(
948 &self,
949 id: &ArchiveId,
950 f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
951 ) -> R {
952 self.with_archive_tree(|t| f(t.find(id)))
953 }
954
955 fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf> {
956 self.with_local_archive(id, |a| a.map(|a| a.path().to_path_buf()))
957 }
958
959 #[allow(clippy::significant_drop_tightening)]
960 fn get_document(&self, uri: &DocumentURI) -> Option<Document> {
961 {
962 let lock = self.cache.read();
963 if let Some(doc) = lock.has_document(uri) {
964 return Some(doc.clone());
965 }
966 }
967 let mut cache = self.cache.write();
968 let mut flattener = GlobalFlattener(&mut cache, &self.archives);
969 flattener.load_document(uri.as_path(), uri.language(), uri.name().first_name())
970 }
971
972 #[allow(clippy::significant_drop_tightening)]
973 fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike> {
974 {
975 let lock = self.cache.read();
976 if uri.name().is_simple() {
977 if let Some(m) = lock.has_module(uri) {
978 return Some(ModuleLike::Module(m.clone()));
979 }
980 } else {
981 let top_uri = !uri.clone();
982 if let Some(m) = lock.has_module(&top_uri) {
983 return ModuleLike::in_module(m, uri.name());
984 }
985 }
986 }
987 let m = {
988 let mut cache = self.cache.write();
989 let mut flattener = GlobalFlattener(&mut cache, &self.archives);
990 flattener.load_module(uri.as_path(), uri.name().first_name())?
991 };
992 ModuleLike::in_module(&m, uri.name())
994 }
995}
996
997#[derive(Debug)]
998struct TemporaryBackendI {
999 modules: parking_lot::Mutex<HMap<ModuleURI, Module>>,
1000 documents: parking_lot::Mutex<HMap<DocumentURI, Document>>,
1001 html: parking_lot::Mutex<HMap<DocumentURI, HTMLData>>,
1002 parent: AnyBackend,
1003}
1004
1005#[derive(Clone, Debug)]
1006pub struct TemporaryBackend {
1007 inner: triomphe::Arc<TemporaryBackendI>,
1008}
1009impl Default for TemporaryBackend {
1010 #[inline]
1011 fn default() -> Self {
1012 Self::new(GlobalBackend::get().to_any())
1013 }
1014}
1015
1016impl TemporaryBackend {
1017 pub fn reset(&self) {
1018 self.inner.modules.lock().clear();
1019 self.inner.documents.lock().clear();
1020 let global = GlobalBackend::get();
1021 global.reset();
1022 }
1023
1024 #[must_use]
1025 pub fn new(parent: AnyBackend) -> Self {
1026 Self {
1027 inner: triomphe::Arc::new(TemporaryBackendI {
1028 modules: parking_lot::Mutex::new(HMap::default()),
1029 documents: parking_lot::Mutex::new(HMap::default()),
1030 html: parking_lot::Mutex::new(HMap::default()),
1031 parent,
1032 }),
1033 }
1034 }
1035 pub fn add_module(&self, m: Module) {
1036 self.inner.modules.lock().insert(m.uri().clone(), m);
1037 }
1038 pub fn add_document(&self, d: Document) {
1039 self.inner.documents.lock().insert(d.uri().clone(), d);
1040 }
1041 pub fn add_html(&self, uri: DocumentURI, d: HTMLData) {
1042 self.inner.html.lock().insert(uri, d);
1043 }
1044}
1045
1046impl Backend for TemporaryBackend {
1047 type ArchiveIter<'a> = EitherArchiveIter<'a>;
1048
1049 #[inline]
1050 fn to_any(&self) -> AnyBackend {
1051 AnyBackend::Temp(self.clone())
1052 }
1053 fn get_document(&self, uri: &DocumentURI) -> Option<Document> {
1054 self.inner
1055 .documents
1056 .lock()
1057 .get(uri)
1058 .cloned()
1059 .or_else(|| self.inner.parent.get_document(uri))
1060 }
1061
1062 #[inline]
1063 fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
1064 where
1065 Self: Sized,
1066 {
1067 self.inner.parent.with_archives(f)
1068 }
1069
1070 fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)> {
1071 self.inner.html.lock().get(d).map_or_else(
1072 || self.inner.parent.get_html_body(d, full),
1073 |html| {
1074 Some((
1075 html.css.clone(),
1076 if full {
1077 html.html[html.body.start..html.body.end].to_string()
1078 } else {
1079 html.html[html.body.start + html.inner_offset..html.body.end].to_string()
1080 },
1081 ))
1082 },
1083 )
1084 }
1085
1086 #[inline]
1087 fn get_html_full(&self, d: &DocumentURI) -> Option<String> {
1088 self.inner.html.lock().get(d).map_or_else(
1089 || self.inner.parent.get_html_full(d),
1090 |html| Some(html.html.clone()),
1091 )
1092 }
1093
1094 fn get_html_fragment(
1095 &self,
1096 d: &DocumentURI,
1097 range: DocumentRange,
1098 ) -> Option<(Vec<CSS>, String)> {
1099 self.inner.html.lock().get(d).map_or_else(
1100 || self.inner.parent.get_html_fragment(d, range),
1101 |html| {
1102 Some((
1103 html.css.clone(),
1104 html.html[range.start..range.end].to_string(),
1105 ))
1106 },
1107 )
1108 }
1109
1110 fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T> {
1111 let lock = self.inner.html.lock();
1112 let Some(html) = lock.get(&rf.in_doc) else {
1113 return self.inner.parent.get_reference(rf);
1114 };
1115
1116 let Some(bytes) = html.refs.as_slice().get(rf.start..rf.end) else {
1117 return Err(eyre::eyre!("reference has invalid start/end points"));
1118 };
1119 let (r, _) = bincode::serde::decode_from_slice(bytes, bincode::config::standard())?;
1120 Ok(r)
1121 }
1122
1123 fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike> {
1124 if uri.name().is_simple() {
1125 return self
1126 .inner
1127 .modules
1128 .lock()
1129 .get(uri)
1130 .cloned()
1131 .map(ModuleLike::Module)
1132 .or_else(|| self.inner.parent.get_module(uri));
1133 }
1134 let top_uri = !uri.clone();
1135 let top = self
1136 .inner
1137 .modules
1138 .lock()
1139 .get(&top_uri)
1140 .cloned()
1141 .or_else(|| match self.inner.parent.get_module(&top_uri) {
1142 Some(ModuleLike::Module(m)) => Some(m),
1143 _ => None,
1144 })?;
1145 ModuleLike::in_module(&top, uri.name())
1146 }
1147 #[inline]
1148 fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf> {
1149 self.inner.parent.get_base_path(id)
1150 }
1151
1152 #[inline]
1153 fn with_archive_or_group<R>(
1154 &self,
1155 id: &ArchiveId,
1156 f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
1157 ) -> R
1158 where
1159 Self: Sized,
1160 {
1161 self.inner.parent.with_archive_or_group(id, f)
1162 }
1163
1164 #[inline]
1165 fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R {
1166 self.inner.parent.with_archive(id, f)
1167 }
1168
1169 #[inline]
1170 fn submit_triples(
1171 &self,
1172 in_doc: &DocumentURI,
1173 rel_path: &str,
1174 iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
1175 ) where
1176 Self: Sized,
1177 {
1178 self.inner.parent.submit_triples(in_doc, rel_path, iter);
1179 }
1180}
1181
1182#[derive(Debug, Clone)]
1183pub enum SandboxedRepository {
1184 Copy(ArchiveId),
1185 Git {
1186 id: ArchiveId,
1187 branch: Box<str>,
1188 commit: flams_git::Commit,
1189 remote: Box<str>,
1190 },
1191}
1192impl SandboxedRepository {
1193 #[inline]
1194 #[must_use]
1195 pub const fn id(&self) -> &ArchiveId {
1196 match self {
1197 Self::Copy(id) | Self::Git { id, .. } => id,
1198 }
1199 }
1200}
1201
1202#[derive(Debug)]
1203pub(super) struct SandboxedBackendI {
1204 path: Box<Path>,
1205 span: tracing::Span,
1206 pub(super) repos: parking_lot::RwLock<Vec<SandboxedRepository>>,
1207 manager: ArchiveManager,
1208 cache: RwLock<cache::BackendCache>,
1209}
1210#[derive(Debug, Clone)]
1211pub struct SandboxedBackend(pub(super) triomphe::Arc<SandboxedBackendI>);
1212impl Drop for SandboxedBackendI {
1213 fn drop(&mut self) {
1214 let _ = std::fs::remove_dir_all(&self.path);
1215 }
1216}
1217impl SandboxedBackend {
1218 #[inline]
1219 #[must_use]
1220 pub fn get_repos(&self) -> Vec<SandboxedRepository> {
1221 self.0.repos.read().clone()
1222 }
1223
1224 #[inline]
1225 pub fn with_repos<R>(&self, f: impl FnOnce(&[SandboxedRepository]) -> R) -> R {
1226 let inner = self.0.repos.read();
1227 f(inner.as_slice())
1228 }
1229
1230 #[inline]
1231 #[must_use]
1232 pub fn path_for(&self, id: &ArchiveId) -> PathBuf {
1233 self.0.path.join(id.as_ref())
1234 }
1235
1236 pub fn new(name: &str) -> Self {
1237 let p = crate::settings::Settings::get().temp_dir().join(name);
1238 let i = SandboxedBackendI {
1239 span: tracing::info_span!(target:"sandbox","sandbox",path=%p.display()),
1240 path: p.into(),
1241 repos: parking_lot::RwLock::new(Vec::new()),
1242 manager: ArchiveManager::default(),
1243 cache: RwLock::new(cache::BackendCache::default()),
1244 };
1245 Self(triomphe::Arc::new(i))
1246 }
1247
1248 #[cfg(feature = "tokio")]
1249 #[tracing::instrument(level = "info",
1250 parent = &self.0.span,
1251 target = "sandbox",
1252 name = "migrating",
1253 fields(path = %self.0.path.display()),
1254 skip_all
1255 )]
1256 pub fn migrate(&self) -> eyre::Result<usize> {
1257 use eyre::Context;
1258 use flams_utils::{impossible, PathExt};
1259
1260 let mut count = 0;
1261 let cnt = &mut count;
1262 let global = GlobalBackend::get();
1263 let mut global_cache = global.cache.write();
1264 let mut sandbox_cache = self.0.cache.write();
1265 self.0.manager.reinit::<eyre::Result<()>>(
1266 move |sandbox| {
1267 global.archives.reinit::<eyre::Result<()>>(
1268 |_| {
1269 sandbox.groups.clear();
1270 let Some(main) = Settings::get().mathhubs.first() else {
1271 unreachable!()
1272 };
1273 for a in std::mem::take(&mut sandbox.archives) {
1274 *cnt += 1;
1275 #[allow(irrefutable_let_patterns)]
1276 let Archive::Local(a) = a
1277 else {
1278 impossible!()
1279 };
1280 let source = a.path();
1281 let target = main.join(a.id().as_ref());
1282
1283 if let Some(p) = target.parent() {
1284 std::fs::create_dir_all(p).wrap_err_with(|| {
1285 format!("Failed to create parent directory for {}", a.id())
1286 })?;
1287 }
1288 let safe_target = unwrap!(target.parent())
1289 .join(format!(".{}.tmp", unwrap!(target.file_name()).display()));
1290 if safe_target.exists() {
1291 std::fs::remove_dir_all(&safe_target).wrap_err_with(|| {
1292 format!(
1293 "Failed to remove existing taget dir {}",
1294 safe_target.display()
1295 )
1296 })?;
1297 }
1298 if let Err(e) = source.rename_safe(&safe_target) {
1299 let e = e.wrap_err(format!("failed to migrate {}", a.id()));
1300 let _ = std::fs::remove_dir_all(safe_target);
1301 return Err(e);
1302 }
1303 if target.exists() {
1304 std::fs::remove_dir_all(&target).wrap_err_with(|| {
1305 format!("Failed to remove original archive {}", a.id())
1306 })?;
1307 }
1308 std::fs::rename(safe_target, target).wrap_err_with(|| {
1309 format!("Failed to install updated archive {}", a.id())
1310 })?;
1311 }
1312 Ok(())
1313 },
1314 Settings::get().mathhubs.iter().map(|p| &**p),
1315 )
1316 },
1317 [&*self.0.path],
1318 )?;
1319 global.triple_store.clear();
1320 global_cache.clear();
1321 sandbox_cache.clear();
1322 drop(global_cache);
1323 drop(sandbox_cache);
1324 flams_utils::background(|| {
1325 let global = GlobalBackend::get();
1326 global.triple_store.load_archives(&global.all_archives());
1327 });
1328 Ok(count)
1329 }
1330
1331 #[tracing::instrument(level = "info",
1332 parent = &self.0.span,
1333 target = "sandbox",
1334 name = "adding",
1335 fields(repository = ?sb),
1336 skip_all
1337 )]
1338 pub fn add(&self, sb: SandboxedRepository, then: impl FnOnce()) {
1339 let mut repos = self.0.repos.write();
1340 let id = sb.id();
1341 if let Some(i) = repos.iter().position(|r| r.id() == id) {
1342 repos.remove(i);
1343 }
1344 self.require_meta_infs(
1345 id,
1346 &mut repos,
1347 |_, _| {},
1348 |_, _, _| {
1349 tracing::error!(target:"sandbox","A group with id {id} already exists!");
1350 },
1351 || {},
1352 );
1353 repos.push(sb);
1354 drop(repos);
1355 then();
1356 self.0.manager.load(&self.0.path);
1357 }
1358
1359 fn require_meta_infs(
1360 &self,
1361 id: &ArchiveId,
1362 repos: &mut Vec<SandboxedRepository>,
1363 then: impl FnOnce(&LocalArchive, &mut Vec<SandboxedRepository>),
1364 group: impl FnOnce(&ArchiveGroup, &ArchiveTree, &mut Vec<SandboxedRepository>),
1365 else_: impl FnOnce(),
1366 ) {
1367 if repos.iter().any(|r| r.id() == id) {
1368 return;
1369 }
1370 let backend = GlobalBackend::get();
1371 backend.manager().with_tree(move |t| {
1372 let mut steps = id.steps();
1373 let Some(mut current) = steps.next() else {
1374 tracing::error!("empty archive ID");
1375 return;
1376 };
1377 let mut ls = &t.groups;
1378 loop {
1379 let Some(a) = ls.iter().find(|a| a.id().last_name() == current) else {
1380 else_();
1381 return;
1382 };
1383 match a {
1384 ArchiveOrGroup::Archive(_) => {
1385 if steps.next().is_some() {
1386 else_();
1387 return;
1388 }
1389 let Some(Archive::Local(a)) = t.get(id) else {
1390 else_();
1391 return;
1392 };
1393 then(a, repos);
1394 return;
1395 }
1396 ArchiveOrGroup::Group(g) => {
1397 let Some(next) = steps.next() else {
1398 group(g, t, repos);
1399 return;
1400 };
1401 if let Some(ArchiveOrGroup::Archive(a)) =
1402 g.children.iter().find(|a| a.id().is_meta())
1403 {
1404 if !repos.iter().any(|r| r.id() == a) {
1405 let Some(Archive::Local(a)) = t.get(a) else {
1406 else_();
1407 return;
1408 };
1409 repos.push(SandboxedRepository::Copy(a.id().clone()));
1410 self.copy_archive(a);
1411 }
1412 }
1413 current = next;
1414 ls = &g.children;
1415 }
1416 }
1417 }
1418 });
1419 }
1420
1421 #[tracing::instrument(level = "info",
1422 parent = &self.0.span,
1423 target = "sandbox",
1424 name = "require",
1425 skip(self)
1426 )]
1427 pub fn require(&self, id: &ArchiveId) {
1428 let mut repos = self.0.repos.write();
1430 self.require_meta_infs(
1431 id,
1432 &mut repos,
1433 |a, repos| {
1434 if !repos.iter().any(|r| r.id() == id) {
1435 repos.push(SandboxedRepository::Copy(id.clone()));
1436 self.copy_archive(a);
1437 }
1438 },
1439 |g, t, repos| {
1440 for a in unwrap!(g.dfs()) {
1441 if let ArchiveOrGroup::Archive(id) = a {
1442 if let Some(Archive::Local(a)) = t.get(id) {
1443 if !repos.iter().any(|r| r.id() == id) {
1444 repos.push(SandboxedRepository::Copy(id.clone()));
1445 self.copy_archive(a);
1446 }
1447 }
1448 }
1449 }
1450 },
1451 || tracing::error!("could not find archive {id}"),
1452 );
1453 drop(repos);
1454 self.0.manager.load(&self.0.path);
1455 }
1456
1457 pub(super) fn copy_archive(&self, a: &LocalArchive) {
1458 let path = a.path();
1459 let target = self.0.path.join(a.id().as_ref());
1460 if target.exists() {
1461 return;
1462 }
1463 tracing::info!("copying archive {} to {}", a.id(), target.display());
1464 if let Err(e) = flams_utils::fs::copy_dir_all(path, &target) {
1465 tracing::error!("could not copy archive {}: {e}", a.id());
1466 }
1467 if !target.exists() || !target.is_dir() {
1468 tracing::error!(
1469 "could not copy archive {}: Target directory does not exist",
1470 a.id()
1471 );
1472 }
1473 }
1474}
1475
1476impl Backend for SandboxedBackend {
1477 type ArchiveIter<'a> =
1478 std::iter::Chain<std::slice::Iter<'a, Archive>, std::slice::Iter<'a, Archive>>;
1479
1480 #[inline]
1481 fn to_any(&self) -> AnyBackend {
1482 AnyBackend::Sandbox(self.clone())
1483 }
1484
1485 fn get_html_fragment(
1486 &self,
1487 d: &DocumentURI,
1488 range: DocumentRange,
1489 ) -> Option<(Vec<CSS>, String)> {
1490 self.with_archive(d.archive_id(), |a| {
1491 a.and_then(|a| {
1492 a.load_html_fragment(d.path(), d.name().first_name(), d.language(), range)
1493 })
1494 })
1495 }
1496 fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T> {
1497 self.with_archive(rf.in_doc.archive_id(), |a| {
1498 let Some(a) = a else {
1499 return Err(eyre::eyre!("Archive {} not found", rf.in_doc.archive_id()));
1500 };
1501 a.load_reference(
1502 rf.in_doc.path(),
1503 rf.in_doc.name().first_name(),
1504 rf.in_doc.language(),
1505 DocumentRange {
1506 start: rf.start,
1507 end: rf.end,
1508 },
1509 )
1510 })
1511 }
1512
1513 #[inline]
1514 fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
1515 where
1516 Self: Sized,
1517 {
1518 self.0.manager.with_tree(|t1| {
1519 GlobalBackend::get()
1520 .with_archive_tree(|t2| f(t1.archives.iter().chain(t2.archives.iter())))
1521 })
1522 }
1523
1524 fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)> {
1525 self.with_archive(d.archive_id(), |a| {
1526 a.and_then(|a| a.load_html_body(d.path(), d.name().first_name(), d.language(), full))
1527 })
1528 }
1529
1530 #[inline]
1531 fn get_html_full(&self, d: &DocumentURI) -> Option<String> {
1532 self.with_archive(d.archive_id(), |a| {
1533 a.and_then(|a| a.load_html_full(d.path(), d.name().first_name(), d.language()))
1534 })
1535 }
1536
1537 fn submit_triples(
1538 &self,
1539 in_doc: &DocumentURI,
1540 rel_path: &str,
1541 iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
1542 ) {
1543 self.0.manager.with_archive(in_doc.archive_id(), |a| {
1544 if let Some(a) = a {
1545 a.submit_triples(
1546 in_doc,
1547 rel_path,
1548 GlobalBackend::get().triple_store(),
1549 false,
1550 iter,
1551 );
1552 }
1553 });
1554 }
1555
1556 fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R {
1557 if let Some(r) = self
1558 .0
1559 .manager
1560 .all_archives()
1561 .iter()
1562 .find(|a| a.uri().archive_id() == id)
1563 {
1564 return f(Some(r));
1565 };
1566 GlobalBackend::get().with_archive(id, f)
1567 }
1568
1569 fn with_archive_or_group<R>(
1570 &self,
1571 id: &ArchiveId,
1572 f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
1573 ) -> R {
1574 let cell = std::cell::Cell::new(Some(f));
1575 if let Some(r) = self.0.manager.with_tree(|t| {
1576 t.find(id)
1577 .map(|a| (cell.take().unwrap_or_else(|| unreachable!()))(Some(a)))
1578 }) {
1579 return r;
1580 };
1581 let f = cell.take().unwrap_or_else(|| unreachable!());
1582 GlobalBackend::get().with_archive_or_group(id, f)
1583 }
1584
1585 fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf> {
1586 self.with_local_archive(id, |a| a.map(|a| a.path().to_path_buf()))
1587 }
1588
1589 fn get_document(&self, uri: &DocumentURI) -> Option<Document> {
1590 let id = uri.archive_id();
1591 if self.0.manager.with_archive(id, |a| a.is_none()) {
1592 return GlobalBackend::get().get_document(uri);
1593 }
1594 {
1595 let lock = self.0.cache.read();
1596 if let Some(doc) = lock.has_document(uri) {
1597 return Some(doc.clone());
1598 }
1599 }
1600 let mut cache = self.0.cache.write();
1601 let mut flattener =
1602 SandboxFlattener(&mut cache, &self.0.manager, &GlobalBackend::get().archives);
1603 let r = flattener.load_document(uri.as_path(), uri.language(), uri.name().first_name());
1604 drop(cache);
1605 r
1606 }
1607
1608 #[allow(clippy::significant_drop_tightening)]
1609 fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike> {
1610 let id = uri.archive_id();
1611 if self.0.manager.with_archive(id, |a| a.is_none()) {
1612 return GlobalBackend::get().get_module(uri);
1613 }
1614 {
1615 let lock = self.0.cache.read();
1616 if uri.name().is_simple() {
1617 if let Some(m) = lock.has_module(uri) {
1618 return Some(ModuleLike::Module(m.clone()));
1619 }
1620 } else {
1621 let top_uri = !uri.clone();
1622 if let Some(m) = lock.has_module(&top_uri) {
1623 return ModuleLike::in_module(m, uri.name());
1624 }
1625 }
1626 }
1627 let m = {
1628 let mut cache = self.0.cache.write();
1629 let mut flattener =
1630 SandboxFlattener(&mut cache, &self.0.manager, &GlobalBackend::get().archives);
1631 flattener.load_module(uri.as_path(), uri.name().first_name())?
1632 };
1633 ModuleLike::in_module(&m, uri.name())
1635 }
1636}
1637
1638pub struct AsChecker<'a, B: Backend>(&'a B);
1639
1640impl<B: Backend> LocalBackend for AsChecker<'_, B> {
1641 #[inline]
1642 fn get_document(&mut self, uri: &DocumentURI) -> Option<Document> {
1643 self.0.get_document(uri)
1644 }
1645 #[inline]
1646 fn get_declaration<T: DeclarationTrait>(
1647 &mut self,
1648 uri: &SymbolURI,
1649 ) -> Option<ContentReference<T>> {
1650 self.0.get_declaration(uri)
1651 }
1652 #[inline]
1653 fn get_module(&mut self, uri: &ModuleURI) -> Option<ModuleLike> {
1654 self.0.get_module(uri)
1655 }
1656}
1657
1658impl<B: Backend> DocumentChecker for AsChecker<'_, B> {
1659 #[inline]
1660 fn open(&mut self, _elem: &mut DocumentElement<Unchecked>) {}
1661 #[inline]
1662 fn close(&mut self, _elem: &mut DocumentElement<Checked>) {}
1663}
1664
1665impl<B: Backend> ModuleChecker for AsChecker<'_, B> {
1666 #[inline]
1667 fn open(&mut self, _elem: &mut OpenDeclaration<Unchecked>) {}
1668 #[inline]
1669 fn close(&mut self, _elem: &mut Declaration) {}
1670}
1671
1672struct GlobalFlattener<'a>(&'a mut BackendCache, &'a ArchiveManager);
1673impl GlobalFlattener<'_> {
1674 fn load_document(
1675 &mut self,
1676 path: PathURIRef,
1677 language: Language,
1678 name: &NameStep,
1679 ) -> Option<Document> {
1680 let pre = self.1.load_document(path, language, name)?;
1682 let doc_file = pre.check(self);
1683 let doc = doc_file.clone();
1684 self.0.insert_document(doc_file);
1685 Some(doc)
1686 }
1687 fn load_module(&mut self, path: PathURIRef, name: &NameStep) -> Option<Module> {
1688 let pre = self.1.load_module(path, name)?;
1690 let module = pre.check(self);
1691 self.0.insert_module(module.clone());
1692 Some(module)
1693 }
1694}
1695
1696impl LocalBackend for GlobalFlattener<'_> {
1697 #[allow(clippy::option_if_let_else)]
1698 fn get_document(&mut self, uri: &DocumentURI) -> Option<Document> {
1699 if let Some(doc) = self.0.has_document(uri) {
1700 Some(doc.clone())
1701 } else {
1702 self.load_document(uri.as_path(), uri.language(), uri.name().first_name())
1703 }
1704 }
1705
1706 fn get_module(&mut self, uri: &ModuleURI) -> Option<ModuleLike> {
1707 if uri.name().is_simple() {
1708 if let Some(m) = self.0.has_module(uri) {
1709 return Some(ModuleLike::Module(m.clone()));
1710 }
1711 } else {
1712 let top_uri = !uri.clone();
1713 if let Some(m) = self.0.has_module(&top_uri) {
1714 return ModuleLike::in_module(m, uri.name());
1715 }
1716 }
1717 let m = self.load_module(uri.as_path(), uri.name().first_name())?;
1718 ModuleLike::in_module(&m, uri.name())
1720 }
1721
1722 fn get_declaration<T: DeclarationTrait>(
1723 &mut self,
1724 uri: &SymbolURI,
1725 ) -> Option<flams_ontology::content::ContentReference<T>> {
1726 let m = self.get_module(uri.module())?;
1727 ContentReference::new(&m, uri.name())
1729 }
1730}
1731
1732impl DocumentChecker for GlobalFlattener<'_> {
1733 #[inline]
1734 fn open(&mut self, _elem: &mut DocumentElement<Unchecked>) {}
1735 #[inline]
1736 fn close(&mut self, _elem: &mut DocumentElement<Checked>) {}
1737}
1738
1739impl ModuleChecker for GlobalFlattener<'_> {
1740 #[inline]
1741 fn open(&mut self, _elem: &mut OpenDeclaration<Unchecked>) {}
1742 #[inline]
1743 fn close(&mut self, _elem: &mut Declaration) {}
1744}
1745
1746struct SandboxFlattener<'a>(&'a mut BackendCache, &'a ArchiveManager, &'a ArchiveManager);
1747impl SandboxFlattener<'_> {
1748 fn load_document(
1749 &mut self,
1750 path: PathURIRef,
1751 language: Language,
1752 name: &NameStep,
1753 ) -> Option<Document> {
1754 let be = if self.1.with_archive(path.archive_id(), |a| a.is_some()) {
1755 self.1
1756 } else {
1757 self.2
1758 };
1759 let pre = be.load_document(path, language, name)?;
1761 let doc_file = pre.check(self);
1762 let doc = doc_file.clone();
1763 self.0.insert_document(doc_file);
1764 Some(doc)
1765 }
1766 fn load_module(&mut self, path: PathURIRef, name: &NameStep) -> Option<Module> {
1767 let be = if self.1.with_archive(path.archive_id(), |a| a.is_some()) {
1768 self.1
1769 } else {
1770 self.2
1771 };
1772 let pre = be.load_module(path, name)?;
1774 let module = pre.check(self);
1775 self.0.insert_module(module.clone());
1776 Some(module)
1777 }
1778}
1779
1780impl LocalBackend for SandboxFlattener<'_> {
1781 #[allow(clippy::option_if_let_else)]
1782 fn get_document(&mut self, uri: &DocumentURI) -> Option<Document> {
1783 if let Some(doc) = self.0.has_document(uri) {
1784 Some(doc.clone())
1785 } else {
1786 self.load_document(uri.as_path(), uri.language(), uri.name().first_name())
1787 }
1788 }
1789
1790 fn get_module(&mut self, uri: &ModuleURI) -> Option<ModuleLike> {
1791 if uri.name().is_simple() {
1792 if let Some(m) = self.0.has_module(uri) {
1793 return Some(ModuleLike::Module(m.clone()));
1794 }
1795 } else {
1796 let top_uri = !uri.clone();
1797 if let Some(m) = self.0.has_module(&top_uri) {
1798 return ModuleLike::in_module(m, uri.name());
1799 }
1800 }
1801 let m = self.load_module(uri.as_path(), uri.name().first_name())?;
1802 ModuleLike::in_module(&m, uri.name())
1804 }
1805
1806 fn get_declaration<T: DeclarationTrait>(
1807 &mut self,
1808 uri: &SymbolURI,
1809 ) -> Option<flams_ontology::content::ContentReference<T>> {
1810 let m = self.get_module(uri.module())?;
1811 ContentReference::new(&m, uri.name())
1813 }
1814}
1815
1816impl DocumentChecker for SandboxFlattener<'_> {
1817 #[inline]
1818 fn open(&mut self, _elem: &mut DocumentElement<Unchecked>) {}
1819 #[inline]
1820 fn close(&mut self, _elem: &mut DocumentElement<Checked>) {}
1821}
1822
1823impl ModuleChecker for SandboxFlattener<'_> {
1824 #[inline]
1825 fn open(&mut self, _elem: &mut OpenDeclaration<Unchecked>) {}
1826 #[inline]
1827 fn close(&mut self, _elem: &mut Declaration) {}
1828}
1829
1830pub struct TermPresenter<'a, W: std::fmt::Write, B: Backend> {
1831 out: W,
1832 backend: &'a B,
1833 in_text: bool,
1834 cache: VecMap<SymbolURI, Option<Rc<Notation>>>,
1835 op_cache: VecMap<SymbolURI, Option<Rc<Notation>>>,
1836 var_cache: VecMap<DocumentElementURI, Option<Rc<Notation>>>,
1837 var_op_cache: VecMap<DocumentElementURI, Option<Rc<Notation>>>,
1838}
1839impl<'a, W: std::fmt::Write, B: Backend> TermPresenter<'a, W, B> {
1840 #[inline]
1841 pub fn new_with_writer(out: W, backend: &'a B, in_text: bool) -> Self {
1842 Self {
1843 out,
1844 backend,
1845 in_text,
1846 cache: VecMap::default(),
1847 op_cache: VecMap::default(),
1848 var_cache: VecMap::default(),
1849 var_op_cache: VecMap::default(),
1850 }
1851 }
1852 #[inline]
1853 pub fn close(self) -> W {
1854 self.out
1855 }
1856
1857 #[inline]
1858 pub const fn backend(&self) -> &'a B {
1859 self.backend
1860 }
1861
1862 fn load_notation(backend: &B, uri: &SymbolURI, needs_op: bool) -> Option<Notation> {
1863 use flams_ontology::rdf::ontologies::ulo2;
1864 use rdf::sparql::{Select, Var};
1865 let iri = uri.to_iri();
1866 let q = Select {
1867 subject: Var('n'),
1868 pred: ulo2::NOTATION_FOR.into_owned(),
1869 object: iri,
1870 };
1871 let iter = GlobalBackend::get().triple_store().query(q.into()).ok()?;
1872 iter.into_uris().find_map(|uri| {
1873 let elem = backend.get_document_element::<DocumentElement<Checked>>(&uri)?;
1874 let DocumentElement::Notation { notation, .. } = elem.as_ref() else {
1875 return None;
1876 };
1877 let r = backend.get_reference(notation).ok()?;
1879 if r.is_op() || !needs_op {
1880 Some(r)
1881 } else {
1882 None
1883 }
1884 })
1885 }
1886
1887 fn load_var_notation(
1888 backend: &B,
1889 uri: &DocumentElementURI,
1890 needs_op: bool,
1891 ) -> Option<Notation> {
1892 let parent = uri.parent();
1893 let parent = backend.get_document_element::<DocumentElement<Checked>>(&parent)?;
1895 let mut ch = parent.as_ref().children().iter();
1896 let mut stack = Vec::new();
1897 loop {
1898 let Some(next) = ch.next() else {
1899 if let Some(n) = stack.pop() {
1900 ch = n;
1901 continue;
1902 }
1903 return None;
1904 };
1905 let not = match next {
1906 DocumentElement::Module { children, .. }
1907 | DocumentElement::Section(Section { children, .. })
1908 | DocumentElement::Morphism { children, .. }
1909 | DocumentElement::MathStructure { children, .. }
1910 | DocumentElement::Extension { children, .. }
1911 | DocumentElement::Paragraph(LogicalParagraph { children, .. })
1912 | DocumentElement::Problem(Problem { children, .. }) => {
1913 let old = std::mem::replace(&mut ch, children.iter());
1914 stack.push(old);
1915 continue;
1916 }
1917 DocumentElement::VariableNotation {
1918 variable, notation, ..
1919 } if variable == uri => notation,
1920 _ => continue,
1921 };
1922 let Some(r) = backend.get_reference(not).ok() else {
1923 continue;
1924 };
1925 if r.is_op() || !needs_op {
1926 return Some(r);
1927 }
1928 }
1929 }
1930}
1931
1932impl<W: std::fmt::Write, B: Backend> std::fmt::Write for TermPresenter<'_, W, B> {
1933 #[inline]
1934 fn write_str(&mut self, s: &str) -> std::fmt::Result {
1935 self.out.write_str(s)
1936 }
1937}
1938impl<W: std::fmt::Write, B: Backend> Presenter for TermPresenter<'_, W, B> {
1939 type N = Rc<Notation>;
1940 #[inline]
1941 fn cont(&mut self, tm: &flams_ontology::content::terms::Term) -> Result<(), PresentationError> {
1942 tm.present(self)
1943 }
1944
1945 #[inline]
1946 fn in_text(&self) -> bool {
1947 self.in_text
1948 }
1949
1950 fn get_notation(&mut self, uri: &SymbolURI) -> Option<Self::N> {
1951 if let Some(n) = self.cache.get(uri) {
1953 return n.clone();
1955 };
1956 let r = Self::load_notation(self.backend, uri, false).map(Rc::new);
1957 self.cache.insert(uri.clone(), r.clone());
1958 if let Some(r) = &r {
1960 if r.is_op() {
1961 self.op_cache.insert(uri.clone(), Some(r.clone()));
1962 }
1963 }
1964 r
1965 }
1966
1967 fn get_op_notation(&mut self, uri: &SymbolURI) -> Option<Self::N> {
1968 if let Some(n) = self.op_cache.get(uri) {
1970 return n.clone();
1972 };
1973 let r = Self::load_notation(self.backend, uri, true).map(Rc::new);
1974 self.op_cache.insert(uri.clone(), r.clone());
1975 if self.cache.get(uri).is_none() {
1977 self.cache.insert(uri.clone(), r.clone());
1978 }
1979 r
1980 }
1981
1982 #[inline]
1983 fn get_variable_notation(&mut self, uri: &DocumentElementURI) -> Option<Self::N> {
1984 if let Some(n) = self.var_cache.get(uri) {
1985 return n.clone();
1986 };
1987 let r = Self::load_var_notation(self.backend, uri, false).map(Rc::new);
1988 self.var_cache.insert(uri.clone(), r.clone());
1989 if let Some(r) = &r {
1990 if r.is_op() {
1991 self.var_op_cache.insert(uri.clone(), Some(r.clone()));
1992 }
1993 }
1994 r
1995 }
1996 #[inline]
1997 fn get_variable_op_notation(&mut self, uri: &DocumentElementURI) -> Option<Self::N> {
1998 if let Some(n) = self.var_op_cache.get(uri) {
1999 return n.clone();
2000 };
2001 let r = Self::load_var_notation(self.backend, uri, true).map(Rc::new);
2002 self.var_op_cache.insert(uri.clone(), r.clone());
2003 if self.var_cache.get(uri).is_none() {
2004 self.var_cache.insert(uri.clone(), r.clone());
2005 }
2006 r
2007 }
2008}
2009
2010pub type StringPresenter<'a, B> = TermPresenter<'a, String, B>;
2011
2012impl<'a, B: Backend> StringPresenter<'a, B> {
2013 #[inline]
2014 pub fn new(backend: &'a B, in_text: bool) -> Self {
2015 Self::new_with_writer(String::new(), backend, in_text)
2016 }
2017 #[inline]
2018 pub fn take(&mut self) -> String {
2019 std::mem::take(&mut self.out)
2020 }
2021
2022 pub fn present(&mut self, term: &Term) -> Result<String, PresentationError> {
2024 self.out.clear();
2025 let r = term.present(self);
2027 let s = std::mem::take(&mut self.out);
2028 r.map(|()| s)
2030 }
2031}