1use flams_ontology::{
2 languages::Language,
3 uris::{
4 ArchiveId, ArchiveURI, DocumentElementURI, DocumentURI, ModuleURI, PathURI, SymbolURI, URI,
5 },
6};
7use std::str::FromStr;
8
9macro_rules! charstr {
10 ($c:ident) => {
11 const_str::concat!($c::SEPARATOR)
12 };
13}
14
15#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16pub enum URIKind {
17 Full,
18 Rel,
19 Archive,
20 Path,
21 Document,
22 DocumentElement,
23 Module,
24 Declaration,
25}
26
27#[derive(Clone)]
28pub enum SymURIComponents {
29 Uri(SymbolURI),
30 Comps {
31 a: ArchiveId,
32 p: Option<String>,
33 m: String,
34 s: String,
35 },
36}
37
38impl SymURIComponents {
39 pub fn into_args<
40 R,
41 F: FnOnce(
42 Option<SymbolURI>,
43 Option<ArchiveId>,
44 Option<String>,
45 Option<Language>,
46 Option<String>,
47 Option<String>,
48 ) -> R,
49 >(
50 self,
51 f: F,
52 ) -> R {
53 match self {
54 Self::Uri(uri) => f(Some(uri), None, None, None, None, None),
55 Self::Comps { a, p, m, s } => f(None, Some(a), p, None, Some(m), Some(s)),
56 }
57 }
58}
59
60impl
61 TryFrom<(
62 Option<SymbolURI>,
63 Option<ArchiveId>,
64 Option<String>,
65 Option<String>,
66 Option<String>,
67 )> for SymURIComponents
68{
69 type Error = ();
70 fn try_from(
71 (uri, a, p, m, s): (
72 Option<SymbolURI>,
73 Option<ArchiveId>,
74 Option<String>,
75 Option<String>,
76 Option<String>,
77 ),
78 ) -> Result<Self, ()> {
79 if let Some(uri) = uri {
80 return if a.is_none() && p.is_none() && m.is_none() && s.is_none() {
81 Ok(Self::Uri(uri))
82 } else {
83 Err(())
84 };
85 }
86 a.map_or_else(
87 || Err(()),
88 |a| match (m, s) {
89 (Some(m), Some(s)) => Ok(Self::Comps { a, p, m, s }),
90 _ => Err(()),
91 },
92 )
93 }
94}
95
96#[derive(Clone)]
97pub enum DocURIComponents {
98 Uri(DocumentURI),
99 RelPath(ArchiveId, String),
100 Comps {
101 a: ArchiveId,
102 p: Option<String>,
103 l: Option<Language>,
104 d: String,
105 },
106}
107impl DocURIComponents {
108 pub fn into_args<
109 R,
110 F: FnOnce(
111 Option<DocumentURI>,
112 Option<String>,
113 Option<ArchiveId>,
114 Option<String>,
115 Option<Language>,
116 Option<String>,
117 ) -> R,
118 >(
119 self,
120 f: F,
121 ) -> R {
122 match self {
123 Self::Uri(uri) => f(Some(uri), None, None, None, None, None),
124 Self::RelPath(a, rp) => f(None, Some(rp), Some(a), None, None, None),
125 Self::Comps { a, p, l, d } => f(None, None, Some(a), p, l, Some(d)),
126 }
127 }
128}
129
130impl
131 TryFrom<(
132 Option<DocumentURI>,
133 Option<String>,
134 Option<ArchiveId>,
135 Option<String>,
136 Option<Language>,
137 Option<String>,
138 )> for DocURIComponents
139{
140 type Error = ();
141 fn try_from(
142 (uri, rp, a, p, l, d): (
143 Option<DocumentURI>,
144 Option<String>,
145 Option<ArchiveId>,
146 Option<String>,
147 Option<Language>,
148 Option<String>,
149 ),
150 ) -> Result<Self, ()> {
151 if let Some(uri) = uri {
152 return if rp.is_none() && a.is_none() && p.is_none() && l.is_none() && d.is_none() {
153 Ok(Self::Uri(uri))
154 } else {
155 Err(())
156 };
157 }
158 a.map_or_else(
159 || Err(()),
160 |a| {
161 if let Some(rp) = rp {
162 if p.is_none() && l.is_none() && d.is_none() {
163 Ok(Self::RelPath(a, rp))
164 } else {
165 Err(())
166 }
167 } else if let Some(d) = d {
168 Ok(Self::Comps { a, p, l, d })
169 } else {
170 Err(())
171 }
172 },
173 )
174 }
175}
176
177#[derive(Clone)]
178pub enum URIComponents {
179 Uri(URI),
180 RelPath(ArchiveId, String),
181 DocComps {
182 a: ArchiveId,
183 p: Option<String>,
184 l: Option<Language>,
185 d: String,
186 },
187 ElemComps {
188 a: ArchiveId,
189 p: Option<String>,
190 l: Option<Language>,
191 d: String,
192 e: String,
193 },
194 ModComps {
195 a: ArchiveId,
196 p: Option<String>,
197 m: String,
198 },
199 SymComps {
200 a: ArchiveId,
201 p: Option<String>,
202 m: String,
203 s: String,
204 },
205}
206impl From<DocURIComponents> for URIComponents {
207 fn from(value: DocURIComponents) -> Self {
208 match value {
209 DocURIComponents::Uri(u) => Self::Uri(URI::Narrative(u.into())),
210 DocURIComponents::RelPath(a, rp) => Self::RelPath(a, rp),
211 DocURIComponents::Comps { a, p, l, d } => Self::DocComps { a, p, l, d },
212 }
213 }
214}
215
216impl URIComponents {
217 pub fn into_args<
218 R,
219 F: FnOnce(
220 Option<URI>,
221 Option<String>,
222 Option<ArchiveId>,
223 Option<String>,
224 Option<Language>,
225 Option<String>,
226 Option<String>,
227 Option<String>,
228 Option<String>,
229 Option<URI>,
230 ) -> R,
231 >(
232 self,
233 f: F,
234 ) -> R {
235 match self {
236 Self::Uri(uri) => f(
237 Some(uri),
238 None,
239 None,
240 None,
241 None,
242 None,
243 None,
244 None,
245 None,
246 None,
247 ),
248 Self::RelPath(a, rp) => f(
249 None,
250 Some(rp),
251 Some(a),
252 None,
253 None,
254 None,
255 None,
256 None,
257 None,
258 None,
259 ),
260 Self::DocComps { a, p, l, d } => {
261 f(None, None, Some(a), p, l, Some(d), None, None, None, None)
262 }
263 Self::ElemComps { a, p, l, d, e } => f(
264 None,
265 None,
266 Some(a),
267 p,
268 l,
269 Some(d),
270 Some(e),
271 None,
272 None,
273 None,
274 ),
275 Self::ModComps { a, p, m } => f(
276 None,
277 None,
278 Some(a),
279 p,
280 None,
281 None,
282 None,
283 Some(m),
284 None,
285 None,
286 ),
287 Self::SymComps { a, p, m, s } => f(
288 None,
289 None,
290 Some(a),
291 p,
292 None,
293 None,
294 None,
295 Some(m),
296 Some(s),
297 None,
298 ),
299 }
300 }
301}
302
303#[allow(clippy::many_single_char_names)]
304impl
305 TryFrom<(
306 Option<URI>,
307 Option<String>,
308 Option<ArchiveId>,
309 Option<String>,
310 Option<Language>,
311 Option<String>,
312 Option<String>,
313 Option<String>,
314 Option<String>,
315 )> for URIComponents
316{
317 type Error = ();
318 fn try_from(
319 (uri, rp, a, p, l, d, e, m, s): (
320 Option<URI>,
321 Option<String>,
322 Option<ArchiveId>,
323 Option<String>,
324 Option<Language>,
325 Option<String>,
326 Option<String>,
327 Option<String>,
328 Option<String>,
329 ),
330 ) -> Result<Self, ()> {
331 if let Some(uri) = uri {
332 return if rp.is_none()
333 && a.is_none()
334 && p.is_none()
335 && l.is_none()
336 && d.is_none()
337 && e.is_none()
338 && m.is_none()
339 && s.is_none()
340 {
341 Ok(Self::Uri(uri))
342 } else {
343 Err(())
344 };
345 }
346 a.map_or_else(
347 || Err(()),
348 |a| {
349 if let Some(rp) = rp {
350 if p.is_none() && l.is_none() && d.is_none() && m.is_none() && s.is_none() {
351 Ok(Self::RelPath(a, rp))
352 } else {
353 Err(())
354 }
355 } else if let Some(d) = d {
356 if e.is_none() && m.is_none() && s.is_none() {
357 Ok(Self::DocComps { a, p, l, d })
358 } else if let Some(e) = e {
359 if m.is_none() && s.is_none() {
360 Ok(Self::ElemComps { a, p, l, d, e })
361 } else {
362 Err(())
363 }
364 } else {
365 Err(())
366 }
367 } else if let Some(m) = m {
368 if d.is_none() && e.is_none() && s.is_none() && l.is_none() {
369 Ok(Self::ModComps { a, p, m })
370 } else if let Some(s) = s {
371 if d.is_none() && e.is_none() && l.is_none() {
372 Ok(Self::SymComps { a, p, m, s })
373 } else {
374 Err(())
375 }
376 } else {
377 Err(())
378 }
379 } else {
380 Err(())
381 }
382 },
383 )
384 }
385}
386
387pub trait URIComponentsTrait {
388 fn get(&self, key: &str) -> Option<&str>;
389 fn get_string(&self, key: &str) -> Option<String>;
390
391 fn kind(&self) -> Option<URIKind>;
392 fn as_doc(&self) -> Option<DocURIComponents> {
393 if let Some(uri) = self.get("uri") {
394 return DocumentURI::from_str(uri).ok().map(DocURIComponents::Uri);
395 }
396 let a = self.get(charstr!(ArchiveURI)).map(ArchiveId::new)?;
397 if let Some(rp) = self.get_string("rp") {
398 if self.get(charstr!(DocumentURI)).is_none()
399 && self.get(charstr!(DocumentElementURI)).is_none()
400 && self.get(charstr!(ModuleURI)).is_none()
401 && self.get(charstr!(SymbolURI)).is_none()
402 {
403 Some(DocURIComponents::RelPath(a, rp))
404 } else {
405 None
406 }
407 } else if self.get(charstr!(DocumentElementURI)).is_none()
408 && self.get(charstr!(ModuleURI)).is_none()
409 && self.get(charstr!(SymbolURI)).is_none()
410 {
411 let p = self.get_string(charstr!(PathURI));
412 let l = self.get("l").and_then(|s| Language::from_str(s).ok());
413 let d = self.get_string("d")?;
414 Some(DocURIComponents::Comps { a, p, l, d })
415 } else {
416 None
417 }
418 }
419 fn as_comps(&self) -> Option<URIComponents> {
420 if let Some(uri) = self.get("uri") {
421 return URI::from_str(uri).ok().map(URIComponents::Uri);
422 }
423 let a = self.get(charstr!(ArchiveURI)).map(ArchiveId::new)?;
424 if let Some(rp) = self.get_string("rp") {
425 return if self.get(charstr!(DocumentURI)).is_none()
426 && self.get(charstr!(DocumentElementURI)).is_none()
427 && self.get(charstr!(ModuleURI)).is_none()
428 && self.get(charstr!(SymbolURI)).is_none()
429 {
430 Some(URIComponents::RelPath(a, rp))
431 } else {
432 None
433 };
434 }
435 if let Some(e) = self.get(charstr!(DocumentElementURI)) {
436 let d = self.get(charstr!(DocumentURI))?;
437 return if self.get(charstr!(ModuleURI)).is_none()
438 && self.get(charstr!(SymbolURI)).is_none()
439 {
440 Some(URIComponents::ElemComps {
441 a,
442 p: self.get(charstr!(PathURI)).map(ToString::to_string),
443 l: self.get("l").and_then(|s| Language::from_str(s).ok()),
444 d: d.to_string(),
445 e: e.to_string(),
446 })
447 } else {
448 None
449 };
450 }
451 if let Some(d) = self.get(charstr!(DocumentURI)) {
452 return if self.get(charstr!(ModuleURI)).is_none()
453 && self.get(charstr!(SymbolURI)).is_none()
454 {
455 Some(URIComponents::DocComps {
456 a,
457 p: self.get(charstr!(PathURI)).map(ToString::to_string),
458 l: self.get("l").and_then(|s| Language::from_str(s).ok()),
459 d: d.to_string(),
460 })
461 } else {
462 None
463 };
464 }
465 if let Some(s) = self.get(charstr!(SymbolURI)) {
466 let m = self.get(charstr!(ModuleURI))?;
467 return if self.get(charstr!(DocumentURI)).is_none()
468 && self.get(charstr!(DocumentElementURI)).is_none()
469 {
470 Some(URIComponents::SymComps {
471 a,
472 p: self.get(charstr!(PathURI)).map(ToString::to_string),
473 m: m.to_string(),
474 s: s.to_string(),
475 })
476 } else {
477 None
478 };
479 }
480 if let Some(m) = self.get(charstr!(ModuleURI)) {
481 return if self.get(charstr!(DocumentURI)).is_none()
482 && self.get(charstr!(DocumentElementURI)).is_none()
483 {
484 Some(URIComponents::ModComps {
485 a,
486 p: self.get(charstr!(PathURI)).map(ToString::to_string),
487 m: m.to_string(),
488 })
489 } else {
490 None
491 };
492 }
493 None
494 }
495
496 #[cfg(feature = "ssr")]
497 fn parse(&self) -> Option<URI> {
498 if let Some(uri) = self.get("uri") {
499 return URI::from_str(uri).ok();
500 }
501 let a = ArchiveId::new(self.get(charstr!(ArchiveURI))?);
502 if let Some(rp) = self.get("rp") {
503 return from_archive_relpath(&a, rp).map(|r| URI::Narrative(r.into()));
504 }
505 todo!()
506 }
507}
508
509impl URIComponentsTrait for leptos_router::params::ParamsMap {
510 #[inline]
511 fn get(&self, key: &str) -> Option<&str> {
512 self.get_str(key)
513 }
514 #[inline]
515 fn get_string(&self, key: &str) -> Option<String> {
516 self.get(key)
517 }
518 fn kind(&self) -> Option<URIKind> {
519 if self.get("uri").is_some() {
520 return Some(URIKind::Full);
521 }
522 if self.get("rp").is_some() {
523 return Some(URIKind::Rel);
524 }
525 self.get(charstr!(ArchiveURI))?;
526 if self.get(charstr!(DocumentURI)).is_some() {
527 if self.get(charstr!(ModuleURI)).is_some() || self.get(charstr!(SymbolURI)).is_some() {
528 return None;
529 }
530 if self.get(charstr!(DocumentElementURI)).is_some() {
531 Some(URIKind::DocumentElement)
532 } else {
533 Some(URIKind::Document)
534 }
535 } else if self.get(charstr!(ModuleURI)).is_some() {
536 if self.get(charstr!(DocumentElementURI)).is_some() {
537 return None;
538 }
539 if self.get(charstr!(SymbolURI)).is_some() {
540 Some(URIKind::Declaration)
541 } else {
542 Some(URIKind::Module)
543 }
544 } else if self.get(charstr!(PathURI)).is_some() {
545 Some(URIKind::Path)
546 } else {
547 Some(URIKind::Archive)
548 }
549 }
550}
551
552#[cfg(feature = "ssr")]
553mod ssr {
554 use super::{DocURIComponents, SymURIComponents, URIComponents};
555 use flams_ontology::{
556 languages::Language,
557 uris::{
558 ArchiveId, DocumentElementURI, DocumentURI, ModuleURI, Name, SymbolURI, URI,
559 URIRefTrait,
560 },
561 };
562 use flams_system::backend::{Backend, GlobalBackend};
563 use std::str::FromStr;
564
565 impl SymURIComponents {
566 #[must_use]
567 pub fn parse(self) -> Option<SymbolURI> {
568 match self {
569 Self::Uri(uri) => Some(uri),
570 Self::Comps { a, p, m, s } => get_sym_uri(&a, p, &m, &s),
571 }
572 }
573 }
574
575 impl DocURIComponents {
576 #[must_use]
577 pub fn parse(self) -> Option<DocumentURI> {
578 match self {
579 Self::Uri(uri) => Some(uri),
580 Self::RelPath(a, rp) => from_archive_relpath(&a, &rp),
581 Self::Comps { a, p, l, d } => get_doc_uri(
582 &a,
583 p.map(|p| Name::from_str(&p).unwrap_or_else(|_| unreachable!())),
584 l.unwrap_or_default(),
585 Name::from_str(&d).unwrap_or_else(|_| unreachable!()),
586 ),
587 }
588 }
589 }
590
591 impl URIComponents {
592 #[must_use]
593 pub fn parse(self) -> Option<URI> {
594 match self {
595 Self::Uri(uri) => Some(uri),
596 Self::RelPath(a, rp) => {
597 from_archive_relpath(&a, &rp).map(|d| URI::Narrative(d.into()))
598 }
599 Self::DocComps { a, p, l, d } => get_doc_uri(
600 &a,
601 p.map(|p| Name::from_str(&p).unwrap_or_else(|_| unreachable!())),
602 l.unwrap_or_default(),
603 Name::from_str(&d).unwrap_or_else(|_| unreachable!()),
604 )
605 .map(|d| URI::Narrative(d.into())),
606 Self::ElemComps { a, p, l, d, e } => {
607 get_elem_uri(&a, p, l, &d, &e).map(|e| URI::Narrative(e.into()))
608 }
609 Self::ModComps { a, p, m } => {
610 get_mod_uri(&a, p, &m).map(|m| URI::Content(m.into()))
611 }
612 Self::SymComps { a, p, m, s } => {
613 get_sym_uri(&a, p, &m, &s).map(|s| URI::Content(s.into()))
614 }
615 }
616 }
617 }
618
619 #[must_use]
620 pub fn from_archive_relpath(a: &ArchiveId, rp: &str) -> Option<DocumentURI> {
621 let (p, n) = if let Some((p, n)) = rp.rsplit_once('/') {
622 (
623 Some(Name::from_str(p).unwrap_or_else(|_| unreachable!())),
624 n,
625 )
626 } else {
627 (None, rp)
628 };
629 let (n, l) = if let Some((n, l)) = n.rsplit_once('.') {
630 Language::from_str(l).map_or_else(
631 |()| {
632 n.rsplit_once('.').map_or_else(
633 || {
634 (
635 Name::from_str(n).unwrap_or_else(|_| unreachable!()),
636 Language::default(),
637 )
638 },
639 |(n, l)| {
640 (
641 Name::from_str(n).unwrap_or_else(|_| unreachable!()),
642 Language::from_str(l).unwrap_or_default(),
643 )
644 },
645 )
646 },
647 |l| (Name::from_str(n).unwrap_or_else(|_| unreachable!()), l),
648 )
649 } else {
650 (
651 Name::from_str(n).unwrap_or_else(|_| unreachable!()),
652 Language::default(),
653 )
654 };
655 get_doc_uri(a, p, l, n)
656 }
657
658 #[must_use]
659 pub fn get_doc_uri(
660 a: &ArchiveId,
661 p: Option<Name>,
662 l: Language,
663 d: Name,
664 ) -> Option<DocumentURI> {
665 let a = GlobalBackend::get().with_archive(a, |a| a.map(|a| a.uri().owned()))?;
666 let p = if let Some(p) = p { a % p } else { a.into() };
667 Some(p & (d, l))
668 }
669
670 #[must_use]
671 #[allow(clippy::many_single_char_names)]
672 pub fn get_elem_uri(
673 a: &ArchiveId,
674 p: Option<String>,
675 l: Option<Language>,
676 d: &str,
677 e: &str,
678 ) -> Option<DocumentElementURI> {
679 get_doc_uri(
680 a,
681 p.map(|p| Name::from_str(&p).ok())?,
682 l.unwrap_or_default(),
683 Name::from_str(d).ok()?,
684 )
685 .and_then(|d| (d & e).ok())
686 }
687
688 #[must_use]
689 #[allow(clippy::many_single_char_names)]
690 pub fn get_mod_uri(a: &ArchiveId, p: Option<String>, m: &str) -> Option<ModuleURI> {
691 let a = GlobalBackend::get().with_archive(a, |a| a.map(|a| a.uri().owned()))?;
692 let p = if let Some(p) = p {
693 a % Name::from_str(&p).ok()?
694 } else {
695 a.into()
696 };
697 (p | m).ok()
698 }
699
700 #[must_use]
701 #[allow(clippy::many_single_char_names)]
702 pub fn get_sym_uri(a: &ArchiveId, p: Option<String>, m: &str, s: &str) -> Option<SymbolURI> {
703 get_mod_uri(a, p, m).and_then(|m| (m | s).ok())
704 }
705}
706
707#[cfg(feature = "ssr")]
708pub use ssr::*;