1use core::str::FromStr;
2use cosmic_nom::{new_span, Trace};
3use nom::combinator::all_consuming;
4use serde::{Serialize,Deserialize};
5
6use crate::err::{ParseErrs, SpaceErr};
7use crate::{ANONYMOUS, HYPERUSER};
8use crate::loc::{CENTRAL, GLOBAL_EXEC, GLOBAL_LOGGER, GLOBAL_REGISTRY, LOCAL_ENDPOINT, LOCAL_HYPERGATE, LOCAL_PORTAL, PointSegment, PointSegQuery, REMOTE_ENDPOINT, RouteSegQuery, Surface, ToPoint, ToSurface, Variable, Version};
9use crate::parse::{consume_point, consume_point_ctx, Env, point_route_segment, point_selector, point_var, ResolverErr};
10use crate::parse::error::result;
11use crate::selector::Selector;
12use crate::util::ToResolved;
13use crate::wave::{Agent, Recipients, ToRecipients};
14
15#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
16pub enum RouteSeg {
17 This,
18 Local,
19 Remote,
20 Global,
21 Domain(String),
22 Tag(String),
23 Star(String),
24}
25
26impl RouteSegQuery for RouteSeg {
27 fn is_local(&self) -> bool {
28 match self {
29 RouteSeg::This => true,
30 _ => false,
31 }
32 }
33
34 fn is_global(&self) -> bool {
35 match self {
36 RouteSeg::Global => true,
37 _ => false,
38 }
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
43pub enum RouteSegVar {
44 This,
45 Local,
46 Remote,
47 Global,
48 Domain(String),
49 Tag(String),
50 Star(String),
51 Var(Variable),
52}
53
54impl RouteSegQuery for RouteSegVar {
55 fn is_local(&self) -> bool {
56 match self {
57 RouteSegVar::This => true,
58 _ => false,
59 }
60 }
61
62 fn is_global(&self) -> bool {
63 match self {
64 RouteSegVar::Global => true,
65 _ => false,
66 }
67 }
68}
69
70impl TryInto<RouteSeg> for RouteSegVar {
71 type Error = SpaceErr;
72
73 fn try_into(self) -> Result<RouteSeg, Self::Error> {
74 match self {
75 RouteSegVar::This => Ok(RouteSeg::This),
76 RouteSegVar::Local => Ok(RouteSeg::Local),
77 RouteSegVar::Global => Ok(RouteSeg::Global),
78 RouteSegVar::Domain(domain) => Ok(RouteSeg::Domain(domain)),
79 RouteSegVar::Tag(tag) => Ok(RouteSeg::Tag(tag)),
80 RouteSegVar::Star(star) => Ok(RouteSeg::Star(star)),
81 RouteSegVar::Var(var) => Err(ParseErrs::from_range(
82 "variables not allowed in this context",
83 "variable not allowed here",
84 var.trace.range,
85 var.trace.extra,
86 )),
87 RouteSegVar::Remote => Ok(RouteSeg::Remote),
88 }
89 }
90}
91
92impl Into<RouteSegVar> for RouteSeg {
93 fn into(self) -> RouteSegVar {
94 match self {
95 RouteSeg::This => RouteSegVar::This,
96 RouteSeg::Local => RouteSegVar::Local,
97 RouteSeg::Remote => RouteSegVar::Remote,
98 RouteSeg::Global => RouteSegVar::Global,
99 RouteSeg::Domain(domain) => RouteSegVar::Domain(domain),
100 RouteSeg::Tag(tag) => RouteSegVar::Tag(tag),
101 RouteSeg::Star(mesh) => RouteSegVar::Star(mesh),
102 }
103 }
104}
105
106impl ToString for RouteSegVar {
107 fn to_string(&self) -> String {
108 match self {
109 Self::This => ".".to_string(),
110 Self::Local => "LOCAL".to_string(),
111 Self::Remote => "REMOTE".to_string(),
112 Self::Global => "GLOBAL".to_string(),
113 Self::Domain(domain) => domain.clone(),
114 Self::Tag(tag) => {
115 format!("[{}]", tag)
116 }
117 Self::Star(mesh) => {
118 format!("<<{}>>", mesh)
119 }
120 Self::Var(var) => {
121 format!("${{{}}}", var.name)
122 }
123 }
124 }
125}
126
127impl FromStr for RouteSeg {
128 type Err = SpaceErr;
129
130 fn from_str(s: &str) -> Result<Self, Self::Err> {
131 let s = new_span(s);
132 Ok(all_consuming(point_route_segment)(s)?.1)
133 }
134}
135
136impl ToString for RouteSeg {
137 fn to_string(&self) -> String {
138 match self {
139 RouteSeg::This => ".".to_string(),
140 RouteSeg::Domain(domain) => domain.clone(),
141 RouteSeg::Tag(tag) => {
142 format!("[{}]", tag)
143 }
144 RouteSeg::Star(sys) => {
145 format!("<<{}>>", sys)
146 }
147 RouteSeg::Global => "GLOBAL".to_string(),
148 RouteSeg::Local => "LOCAL".to_string(),
149 RouteSeg::Remote => "REMOTE".to_string(),
150 }
151 }
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash, strum_macros::Display)]
155pub enum PointSegKind {
156 Root,
157 Space,
158 Base,
159 FilesystemRootDir,
160 Dir,
161 File,
162 Version,
163 Pop,
164 Working,
165 Var,
166}
167
168impl PointSegKind {
169 pub fn preceding_delim(&self, post_fileroot: bool) -> &'static str {
170 match self {
171 Self::Space => "",
172 Self::Base => ":",
173 Self::Dir => "",
174 Self::File => "",
175 Self::Version => ":",
176 Self::FilesystemRootDir => ":",
177 Self::Root => "",
178 Self::Pop => match post_fileroot {
179 true => "",
180 false => ":",
181 },
182 Self::Working => match post_fileroot {
183 true => "",
184 false => ":",
185 },
186 Self::Var => match post_fileroot {
187 true => "",
188 false => ":",
189 },
190 }
191 }
192
193 pub fn is_normalized(&self) -> bool {
194 match self {
195 Self::Pop => false,
196 Self::Working => false,
197 Self::Var => false,
198 _ => true,
199 }
200 }
201
202 pub fn is_version(&self) -> bool {
203 match self {
204 Self::Version => true,
205 _ => false,
206 }
207 }
208
209 pub fn is_file(&self) -> bool {
210 match self {
211 Self::File => true,
212 _ => false,
213 }
214 }
215
216 pub fn is_dir(&self) -> bool {
217 match self {
218 Self::Dir => true,
219 _ => false,
220 }
221 }
222
223 pub fn is_filesystem_seg(&self) -> bool {
224 match self {
225 PointSegKind::Root => false,
226 PointSegKind::Space => false,
227 PointSegKind::Base => false,
228 PointSegKind::FilesystemRootDir => true,
229 PointSegKind::Dir => true,
230 PointSegKind::File => true,
231 PointSegKind::Version => false,
232 PointSegKind::Pop => true,
233 PointSegKind::Working => true,
234 PointSegKind::Var => true,
235 }
236 }
237
238 pub fn is_mesh_seg(&self) -> bool {
239 match self {
240 PointSegKind::Root => true,
241 PointSegKind::Space => true,
242 PointSegKind::Base => true,
243 PointSegKind::FilesystemRootDir => false,
244 PointSegKind::Dir => false,
245 PointSegKind::File => false,
246 PointSegKind::Version => true,
247 PointSegKind::Pop => true,
248 PointSegKind::Working => true,
249 PointSegKind::Var => true,
250 }
251 }
252}
253
254impl PointSegQuery for PointSeg {
255 fn is_filesystem_root(&self) -> bool {
256 match self {
257 Self::FilesystemRootDir => true,
258 _ => false,
259 }
260 }
261 fn kind(&self) -> PointSegKind {
262 match self {
263 PointSeg::Root => PointSegKind::Root,
264 PointSeg::Space(_) => PointSegKind::Space,
265 PointSeg::Base(_) => PointSegKind::Base,
266 PointSeg::FilesystemRootDir => PointSegKind::FilesystemRootDir,
267 PointSeg::Dir(_) => PointSegKind::Dir,
268 PointSeg::File(_) => PointSegKind::File,
269 PointSeg::Version(_) => PointSegKind::Version,
270 }
271 }
272}
273
274impl PointSegQuery for PointSegCtx {
275 fn is_filesystem_root(&self) -> bool {
276 match self {
277 Self::FilesystemRootDir => true,
278 _ => false,
279 }
280 }
281
282 fn kind(&self) -> PointSegKind {
283 match self {
284 Self::Root => PointSegKind::Root,
285 Self::Space(_) => PointSegKind::Space,
286 Self::Base(_) => PointSegKind::Base,
287 Self::FilesystemRootDir => PointSegKind::FilesystemRootDir,
288 Self::Dir(_) => PointSegKind::Dir,
289 Self::File(_) => PointSegKind::File,
290 Self::Version(_) => PointSegKind::Version,
291 Self::Pop { .. } => PointSegKind::Pop,
292 Self::Working { .. } => PointSegKind::Working,
293 }
294 }
295}
296
297impl PointSegQuery for PointSegVar {
298 fn is_filesystem_root(&self) -> bool {
299 match self {
300 Self::FilesystemRootDir => true,
301 _ => false,
302 }
303 }
304
305 fn kind(&self) -> PointSegKind {
306 match self {
307 Self::Root => PointSegKind::Root,
308 Self::Space(_) => PointSegKind::Space,
309 Self::Base(_) => PointSegKind::Base,
310 Self::FilesystemRootDir => PointSegKind::FilesystemRootDir,
311 Self::Dir(_) => PointSegKind::Dir,
312 Self::File(_) => PointSegKind::File,
313 Self::Version(_) => PointSegKind::Version,
314 Self::Pop { .. } => PointSegKind::Pop,
315 Self::Working { .. } => PointSegKind::Working,
316 Self::Var(_) => PointSegKind::Var,
317 }
318 }
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
322pub enum PointSegCtx {
323 Root,
324 Space(String),
325 Base(String),
326 FilesystemRootDir,
327 Dir(String),
328 File(String),
329 Version(Version),
330 Working(Trace),
331 Pop(Trace),
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
335pub enum PointSegVar {
336 Root,
337 Space(String),
338 Base(String),
339 FilesystemRootDir,
340 Dir(String),
341 File(String),
342 Version(Version),
343 Working(Trace),
344 Pop(Trace),
345 Var(Variable),
346}
347
348impl ToString for PointSegVar {
349 fn to_string(&self) -> String {
350 match self {
351 PointSegVar::Root => "".to_string(),
352 PointSegVar::Space(space) => space.clone(),
353 PointSegVar::Base(base) => base.clone(),
354 PointSegVar::FilesystemRootDir => "/".to_string(),
355 PointSegVar::Dir(dir) => dir.clone(),
356 PointSegVar::File(file) => file.clone(),
357 PointSegVar::Version(version) => version.to_string(),
358 PointSegVar::Working(_) => ".".to_string(),
359 PointSegVar::Pop(_) => "..".to_string(),
360 PointSegVar::Var(var) => format!("${{{}}}", var.name),
361 }
362 }
363}
364
365impl PointSegVar {
366 pub fn is_normalized(&self) -> bool {
367 self.kind().is_normalized()
368 }
369
370 pub fn is_filesystem_seg(&self) -> bool {
371 self.kind().is_filesystem_seg()
372 }
373}
374
375impl Into<PointSegVar> for PointSegCtx {
376 fn into(self) -> PointSegVar {
377 match self {
378 PointSegCtx::Root => PointSegVar::Root,
379 PointSegCtx::Space(space) => PointSegVar::Space(space),
380 PointSegCtx::Base(base) => PointSegVar::Base(base),
381 PointSegCtx::FilesystemRootDir => PointSegVar::FilesystemRootDir,
382 PointSegCtx::Dir(dir) => PointSegVar::Dir(dir),
383 PointSegCtx::File(file) => PointSegVar::File(file),
384 PointSegCtx::Version(version) => PointSegVar::Version(version),
385 PointSegCtx::Working(trace) => PointSegVar::Working(trace),
386 PointSegCtx::Pop(trace) => PointSegVar::Pop(trace),
387 }
388 }
389}
390
391impl TryInto<PointSegCtx> for PointSegVar {
392 type Error = SpaceErr;
393
394 fn try_into(self) -> Result<PointSegCtx, Self::Error> {
395 match self {
396 PointSegVar::Root => Ok(PointSegCtx::Root),
397 PointSegVar::Space(space) => Ok(PointSegCtx::Space(space)),
398 PointSegVar::Base(base) => Ok(PointSegCtx::Base(base)),
399 PointSegVar::FilesystemRootDir => Ok(PointSegCtx::FilesystemRootDir),
400 PointSegVar::Dir(dir) => Ok(PointSegCtx::Dir(dir)),
401 PointSegVar::File(file) => Ok(PointSegCtx::File(file)),
402 PointSegVar::Version(version) => Ok(PointSegCtx::Version(version)),
403 PointSegVar::Working(trace) => Err(ParseErrs::from_range(
404 "working point not available in this context",
405 "working point not available",
406 trace.range,
407 trace.extra,
408 )),
409 PointSegVar::Pop(trace) => Err(ParseErrs::from_range(
410 "point pop not available in this context",
411 "point pop not available",
412 trace.range,
413 trace.extra,
414 )),
415 PointSegVar::Var(var) => Err(ParseErrs::from_range(
416 "variable substitution not available in this context",
417 "var subst not available",
418 var.trace.range,
419 var.trace.extra,
420 )),
421 }
422 }
423}
424
425impl TryInto<PointSeg> for PointSegCtx {
426 type Error = SpaceErr;
427
428 fn try_into(self) -> Result<PointSeg, Self::Error> {
429 match self {
430 PointSegCtx::Root => Ok(PointSeg::Root),
431 PointSegCtx::Space(space) => Ok(PointSeg::Space(space)),
432 PointSegCtx::Base(base) => Ok(PointSeg::Base(base)),
433 PointSegCtx::FilesystemRootDir => Ok(PointSeg::FilesystemRootDir),
434 PointSegCtx::Dir(dir) => Ok(PointSeg::Dir(dir)),
435 PointSegCtx::File(file) => Ok(PointSeg::File(file)),
436 PointSegCtx::Version(version) => Ok(PointSeg::Version(version)),
437 PointSegCtx::Working(trace) => Err(ParseErrs::from_range(
438 "working point not available in this context",
439 "working point not available",
440 trace.range,
441 trace.extra,
442 )),
443 PointSegCtx::Pop(trace) => Err(ParseErrs::from_range(
444 "point pop not available in this context",
445 "point pop not available",
446 trace.range,
447 trace.extra,
448 )),
449 }
450 }
451}
452
453impl PointSegCtx {
454 pub fn is_normalized(&self) -> bool {
455 self.kind().is_normalized()
456 }
457
458 pub fn is_filesystem_seg(&self) -> bool {
459 self.kind().is_filesystem_seg()
460 }
461}
462
463#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
464pub enum PointSeg {
465 Root,
466 Space(String),
467 Base(String),
468 FilesystemRootDir,
469 Dir(String),
470 File(String),
471 Version(Version),
472}
473
474impl PointSegment for PointSeg {}
475
476impl PointSegment for PointSegCtx {}
477
478impl PointSegment for PointSegVar {}
479
480impl Into<PointSegCtx> for PointSeg {
481 fn into(self) -> PointSegCtx {
482 match self {
483 PointSeg::Root => PointSegCtx::Root,
484 PointSeg::Space(space) => PointSegCtx::Space(space),
485 PointSeg::Base(base) => PointSegCtx::Base(base),
486 PointSeg::FilesystemRootDir => PointSegCtx::FilesystemRootDir,
487 PointSeg::Dir(dir) => PointSegCtx::Dir(dir),
488 PointSeg::File(file) => PointSegCtx::File(file),
489 PointSeg::Version(version) => PointSegCtx::Version(version),
490 }
491 }
492}
493
494impl PointSeg {
495 pub fn is_file(&self) -> bool {
496 self.kind().is_file()
497 }
498
499 pub fn is_normalized(&self) -> bool {
500 self.kind().is_normalized()
501 }
502
503 pub fn is_version(&self) -> bool {
504 self.kind().is_version()
505 }
506
507 pub fn is_filesystem_seg(&self) -> bool {
508 self.kind().is_filesystem_seg()
509 }
510 pub fn preceding_delim(&self, post_fileroot: bool) -> &str {
511 self.kind().preceding_delim(post_fileroot)
512 }
513}
514
515impl ToString for PointSeg {
516 fn to_string(&self) -> String {
517 match self {
518 PointSeg::Space(space) => space.clone(),
519 PointSeg::Base(base) => base.clone(),
520 PointSeg::Dir(dir) => dir.clone(),
521 PointSeg::File(file) => file.clone(),
522 PointSeg::Version(version) => version.to_string(),
523 PointSeg::FilesystemRootDir => "/".to_string(),
524 PointSeg::Root => "".to_string(),
525 }
526 }
527}
528
529#[derive(Debug, Clone, Serialize, Deserialize)]
530pub enum PointSegDelim {
531 Empty,
532 Mesh,
533 File,
534}
535
536impl ToString for PointSegDelim {
537 fn to_string(&self) -> String {
538 match self {
539 PointSegDelim::Empty => "".to_string(),
540 PointSegDelim::Mesh => ":".to_string(),
541 PointSegDelim::File => "/".to_string(),
542 }
543 }
544}
545
546#[derive(Debug, Clone, Serialize, Deserialize)]
547pub struct PointSegPairDef<Seg> {
548 pub delim: PointSegDelim,
549 pub seg: Seg,
550}
551
552impl<Seg> PointSegPairDef<Seg> {
553 pub fn new(delim: PointSegDelim, seg: Seg) -> Self {
554 Self { delim, seg }
555 }
556}
557
558impl<Seg> ToString for PointSegPairDef<Seg>
559where
560 Seg: ToString,
561{
562 fn to_string(&self) -> String {
563 format!("{}{}", self.delim.to_string(), self.seg.to_string())
564 }
565}
566
567impl Into<Surface> for Point {
568 fn into(self) -> Surface {
569 Surface {
570 point: self,
571 topic: Default::default(),
572 layer: Default::default(),
573 }
574 }
575}
576
577impl ToRecipients for Point {
578 fn to_recipients(self) -> Recipients {
579 self.to_surface().to_recipients()
580 }
581}
582
583impl PointVar {
584 pub fn to_point(self) -> Result<Point, SpaceErr> {
585 self.collapse()
586 }
587
588 pub fn to_point_ctx(self) -> Result<PointCtx, SpaceErr> {
589 self.collapse()
590 }
591}
592
593impl ToPoint for Point {
594 fn to_point(&self) -> Point {
595 self.clone()
596 }
597}
598
599impl ToSurface for Point {
600 fn to_surface(&self) -> Surface {
601 self.clone().into()
602 }
603}
604
605impl ToResolved<Point> for PointVar {
606 fn to_resolved(self, env: &Env) -> Result<Point, SpaceErr> {
607 let point_ctx: PointCtx = self.to_resolved(env)?;
608 point_ctx.to_resolved(env)
609 }
610}
611
612impl Into<Selector> for Point {
613 fn into(self) -> Selector {
614 let string = self.to_string();
615 let rtn = result(all_consuming(point_selector)(new_span(string.as_str()))).unwrap();
616 string;
617 rtn
618 }
619}
620
621impl PointCtx {
622 pub fn to_point(self) -> Result<Point, SpaceErr> {
623 self.collapse()
624 }
625}
626
627impl ToResolved<PointCtx> for PointVar {
628 fn collapse(self) -> Result<PointCtx, SpaceErr> {
629 let route = self.route.try_into()?;
630 let mut segments = vec![];
631 for segment in self.segments {
632 segments.push(segment.try_into()?);
633 }
634 Ok(PointCtx { route, segments })
635 }
636
637 fn to_resolved(self, env: &Env) -> Result<PointCtx, SpaceErr> {
638 let mut rtn = String::new();
639 let mut after_fs = false;
640 let mut errs = vec![];
641
642 match &self.route {
643 RouteSegVar::Var(var) => match env.val(var.name.clone().as_str()) {
644 Ok(val) => {
645 let val: String = val.clone().try_into()?;
646 rtn.push_str(format!("{}::", val.as_str()).as_str());
647 }
648 Err(err) => match err {
649 ResolverErr::NotAvailable => {
650 errs.push(ParseErrs::from_range(
651 format!(
652 "variables not available in this context '{}'",
653 var.name.clone()
654 )
655 .as_str(),
656 "Not Available",
657 var.trace.range.clone(),
658 var.trace.extra.clone(),
659 ));
660 }
661 ResolverErr::NotFound => {
662 errs.push(ParseErrs::from_range(
663 format!("variable could not be resolved '{}'", var.name.clone())
664 .as_str(),
665 "Not Found",
666 var.trace.range.clone(),
667 var.trace.extra.clone(),
668 ));
669 }
670 },
671 },
672
673 RouteSegVar::This => {}
674 RouteSegVar::Domain(domain) => {
675 rtn.push_str(format!("{}::", domain).as_str());
676 }
677 RouteSegVar::Tag(tag) => {
678 rtn.push_str(format!("[{}]::", tag).as_str());
679 }
680 RouteSegVar::Star(mesh) => {
681 rtn.push_str(format!("<{}>::", mesh).as_str());
682 }
683 RouteSegVar::Global => {
684 rtn.push_str("GLOBAL::");
685 }
686 RouteSegVar::Local => {
687 rtn.push_str("LOCAL::");
688 }
689 RouteSegVar::Remote => {
690 rtn.push_str("REMOTE::");
691 }
692 };
693
694 if self.segments.len() == 0 {
695 rtn.push_str("ROOT");
696 return consume_point_ctx(rtn.as_str());
697 }
698 for (index, segment) in self.segments.iter().enumerate() {
699 if let PointSegVar::Var(ref var) = segment {
700 match env.val(var.name.clone().as_str()) {
701 Ok(val) => {
702 if index > 1 {
703 if after_fs {
704 } else {
706 rtn.push_str(":");
707 }
708 }
709 let val: String = val.clone().try_into()?;
710 rtn.push_str(val.as_str());
711 }
712 Err(err) => match err {
713 ResolverErr::NotAvailable => {
714 errs.push(ParseErrs::from_range(
715 format!(
716 "variables not available in this context '{}'",
717 var.name.clone()
718 )
719 .as_str(),
720 "Not Available",
721 var.trace.range.clone(),
722 var.trace.extra.clone(),
723 ));
724 }
725 ResolverErr::NotFound => {
726 errs.push(ParseErrs::from_range(
727 format!("variable could not be resolved '{}'", var.name.clone())
728 .as_str(),
729 "Not Found",
730 var.trace.range.clone(),
731 var.trace.extra.clone(),
732 ));
733 }
734 },
735 }
736 } else if PointSegVar::FilesystemRootDir == *segment {
737 after_fs = true;
738 rtn.push_str(":/");
739 } else {
740 if index > 0 {
741 if after_fs {
742 } else {
744 rtn.push_str(":");
745 }
746 }
747 rtn.push_str(segment.to_string().as_str());
748 }
749 }
750 if self.is_dir() {
751 }
753
754 if !errs.is_empty() {
755 let errs = ParseErrs::fold(errs);
756 return Err(errs.into());
757 }
758 consume_point_ctx(rtn.as_str())
759 }
760}
761
762impl ToResolved<Point> for PointCtx {
763 fn collapse(self) -> Result<Point, SpaceErr> {
764 let mut segments = vec![];
765 for segment in self.segments {
766 segments.push(segment.try_into()?);
767 }
768 Ok(Point {
769 route: self.route,
770 segments,
771 })
772 }
773
774 fn to_resolved(self, env: &Env) -> Result<Point, SpaceErr> {
775 if self.segments.is_empty() {
776 return Ok(Point {
777 route: self.route,
778 segments: vec![],
779 });
780 }
781
782 let mut old = self;
783 let mut point = Point::root();
784
785 for (index, segment) in old.segments.iter().enumerate() {
786 match segment {
787 PointSegCtx::Working(trace) => {
788 if index > 1 {
789 return Err(ParseErrs::from_range(
790 "working point can only be referenced in the first point segment",
791 "first segment only",
792 trace.range.clone(),
793 trace.extra.clone(),
794 ));
795 }
796 point = match env.point_or() {
797 Ok(point) => point.clone(),
798 Err(_) => {
799 return Err(ParseErrs::from_range(
800 "working point is not available in this context",
801 "not available",
802 trace.range.clone(),
803 trace.extra.clone(),
804 ));
805 }
806 };
807 }
808 PointSegCtx::Pop(trace) => {
809 if index <= 1 {
810 point = match env.point_or() {
811 Ok(point) => point.clone(),
812 Err(_) => {
813 return Err(ParseErrs::from_range(
814 "cannot pop because working point is not available in this context",
815 "not available",
816 trace.range.clone(),
817 trace.extra.clone(),
818 ));
819 }
820 };
821 }
822 if point.segments.pop().is_none() {
823 return Err(ParseErrs::from_range(
824 format!(
825 "Too many point pops. working point was: '{}'",
826 env.point_or().unwrap().to_string()
827 )
828 .as_str(),
829 "too many point pops",
830 trace.range.clone(),
831 trace.extra.clone(),
832 ));
833 }
834 }
835 PointSegCtx::FilesystemRootDir => {
836 point = point.push("/".to_string())?;
837 }
838 PointSegCtx::Root => {
839 }
841 PointSegCtx::Space(space) => point = point.push(space.clone())?,
842 PointSegCtx::Base(base) => point = point.push(base.clone())?,
843 PointSegCtx::Dir(dir) => point = point.push(dir.clone())?,
844 PointSegCtx::File(file) => point = point.push(file.clone())?,
845 PointSegCtx::Version(version) => point = point.push(version.to_string())?,
846 }
847 }
848
849 Ok(point)
850 }
851}
852
853impl TryInto<Point> for PointCtx {
854 type Error = SpaceErr;
855
856 fn try_into(self) -> Result<Point, Self::Error> {
857 let mut rtn = vec![];
858 for segment in self.segments {
859 rtn.push(segment.try_into()?);
860 }
861 Ok(Point {
862 route: self.route,
863 segments: rtn,
864 })
865 }
866}
867
868impl TryFrom<String> for Point {
869 type Error = SpaceErr;
870
871 fn try_from(value: String) -> Result<Self, Self::Error> {
872 consume_point(value.as_str())
873 }
874}
875
876impl TryFrom<&str> for Point {
877 type Error = SpaceErr;
878
879 fn try_from(value: &str) -> Result<Self, Self::Error> {
880 consume_point(value)
881 }
882}
883
884#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
885pub struct PointDef<Route, Seg> {
886 pub route: Route,
887 pub segments: Vec<Seg>,
888}
889
890impl<Route, Seg> PointDef<Route, Seg>
891where
892 Route: Clone,
893 Seg: Clone,
894{
895 pub fn parent(&self) -> Option<PointDef<Route, Seg>> {
896 if self.segments.is_empty() {
897 return None;
898 }
899 let mut segments = self.segments.clone();
900 segments.remove(segments.len() - 1);
901 Some(Self {
902 route: self.route.clone(),
903 segments,
904 })
905 }
906
907 pub fn last_segment(&self) -> Option<Seg> {
908 self.segments.last().cloned()
909 }
910
911 pub fn is_root(&self) -> bool {
912 self.segments.is_empty()
913 }
914}
915
916impl Point {
917 pub fn to_agent(&self) -> Agent {
918 if *self == *HYPERUSER {
919 Agent::HyperUser
920 } else if *self == *ANONYMOUS {
921 Agent::Anonymous
922 } else {
923 Agent::Point(self.clone())
924 }
925 }
926
927 pub fn is_global(&self) -> bool {
928 match self.route {
929 RouteSeg::Global => true,
930 _ => false,
931 }
932 }
933
934 pub fn is_parent_of(&self, point: &Point) -> bool {
935 if self.segments.len() > point.segments.len() {
936 return false;
937 }
938
939 if self.route != point.route {
940 return false;
941 }
942
943 for i in 0..self.segments.len() {
944 if *self.segments.get(i).as_ref().unwrap() != *point.segments.get(i).as_ref().unwrap() {
945 return false;
946 }
947 }
948 true
949 }
950
951 pub fn central() -> Self {
952 CENTRAL.clone()
953 }
954
955 pub fn global_executor() -> Self {
956 GLOBAL_EXEC.clone()
957 }
958
959 pub fn global_logger() -> Self {
960 GLOBAL_LOGGER.clone()
961 }
962
963 pub fn global_registry() -> Self {
964 GLOBAL_REGISTRY.clone()
965 }
966
967 pub fn local_portal() -> Self {
968 LOCAL_PORTAL.clone()
969 }
970
971 pub fn local_hypergate() -> Self {
972 LOCAL_HYPERGATE.clone()
973 }
974
975 pub fn local_endpoint() -> Self {
976 LOCAL_ENDPOINT.clone()
977 }
978
979 pub fn remote_endpoint() -> Self {
980 REMOTE_ENDPOINT.clone()
981 }
982
983 pub fn hyperuser() -> Self {
984 HYPERUSER.clone()
985 }
986
987 pub fn anonymous() -> Self {
988 ANONYMOUS.clone()
989 }
990
991 pub fn normalize(self) -> Result<Point, SpaceErr> {
992 if self.is_normalized() {
993 return Ok(self);
994 }
995
996 if !self
997 .segments
998 .first()
999 .expect("expected first segment")
1000 .is_normalized()
1001 {
1002 return Err(format!("absolute point paths cannot begin with '..' (reference parent segment) because there is no working point segment: '{}'",self.to_string()).into());
1003 }
1004
1005 let mut segments = vec![];
1006 for seg in &self.segments {
1007 match seg.is_normalized() {
1008 true => segments.push(seg.clone()),
1009 false => {
1010 if segments.pop().is_none() {
1011 return Err(format!(
1012 "'..' too many pop segments directives: out of parents: '{}'",
1013 self.to_string()
1014 )
1015 .into());
1016 }
1017 }
1018 }
1019 }
1020 Ok(Point {
1021 route: self.route,
1022 segments,
1023 })
1024 }
1025
1026 pub fn is_parent(&self, child: &Point) -> Result<(), ()> {
1027 if self.route != child.route {
1028 return Err(());
1029 }
1030
1031 if self.segments.len() >= child.segments.len() {
1032 return Err(());
1033 }
1034
1035 for (index, seg) in self.segments.iter().enumerate() {
1036 if *seg != *child.segments.get(index).unwrap() {
1037 return Err(());
1038 }
1039 }
1040
1041 Ok(())
1042 }
1043
1044 pub fn is_normalized(&self) -> bool {
1045 for seg in &self.segments {
1046 if !seg.is_normalized() {
1047 return false;
1048 }
1049 }
1050 true
1051 }
1052
1053 pub fn to_bundle(self) -> Result<Point, SpaceErr> {
1054 if self.segments.is_empty() {
1055 return Err("Point does not contain a bundle".into());
1056 }
1057
1058 if let Some(PointSeg::Version(_)) = self.segments.last() {
1059 return Ok(self);
1060 }
1061
1062 return self.parent().expect("expected parent").to_bundle();
1063 }
1064
1065 pub fn has_bundle(&self) -> bool {
1066 if self.segments.is_empty() {
1067 return false;
1068 }
1069
1070 if let Some(PointSeg::Version(_)) = self.segments.last() {
1071 return true;
1072 }
1073
1074 return self.parent().expect("expected parent").to_bundle().is_ok();
1075 }
1076
1077 pub fn to_safe_filename(&self) -> String {
1078 self.to_string()
1079 }
1080
1081 pub fn has_filesystem(&self) -> bool {
1082 for segment in &self.segments {
1083 match segment {
1084 PointSeg::FilesystemRootDir => {
1085 return true;
1086 }
1087 _ => {}
1088 }
1089 }
1090 false
1091 }
1092
1093 pub fn is_artifact_bundle_part(&self) -> bool {
1094 for segment in &self.segments {
1095 if segment.is_version() {
1096 return true;
1097 }
1098 }
1099 return false;
1100 }
1101
1102 pub fn is_artifact(&self) -> bool {
1103 if let Option::Some(segment) = self.last_segment() {
1104 if self.is_artifact_bundle_part() && segment.is_file() {
1105 true
1106 } else {
1107 false
1108 }
1109 } else {
1110 false
1111 }
1112 }
1113
1114 pub fn is_artifact_bundle(&self) -> bool {
1115 if let Option::Some(segment) = self.last_segment() {
1116 segment.is_version()
1117 } else {
1118 false
1119 }
1120 }
1121
1122 pub fn pop(&self) -> Self {
1123 let mut segments = self.segments.clone();
1124 segments.pop();
1125 Point {
1126 route: self.route.clone(),
1127 segments,
1128 }
1129 }
1130 pub fn push<S: ToString>(&self, segment: S) -> Result<Self, SpaceErr> {
1131 let segment = segment.to_string();
1132 if self.segments.is_empty() {
1133 Self::from_str(segment.as_str())
1134 } else {
1135 let last = self.last_segment().expect("expected last segment");
1136 let point = match last {
1137 PointSeg::Root => segment,
1138 PointSeg::Space(_) => {
1139 format!("{}:{}", self.to_string(), segment)
1140 }
1141 PointSeg::Base(_) => {
1142 format!("{}:{}", self.to_string(), segment)
1143 }
1144 PointSeg::FilesystemRootDir => {
1145 format!("{}{}", self.to_string(), segment)
1146 }
1147 PointSeg::Dir(_) => {
1148 format!("{}{}", self.to_string(), segment)
1149 }
1150 PointSeg::Version(_) => {
1151 if segment.starts_with(":") {
1152 format!("{}{}", self.to_string(), segment)
1153 } else {
1154 format!("{}:{}", self.to_string(), segment)
1155 }
1156 }
1157 PointSeg::File(_) => return Err("cannot append to a file".into()),
1158 };
1159 Self::from_str(point.as_str())
1160 }
1161 }
1162
1163 pub fn push_file(&self, segment: String) -> Result<Self, SpaceErr> {
1164 Self::from_str(format!("{}{}", self.to_string(), segment).as_str())
1165 }
1166
1167 pub fn push_segment(&self, segment: PointSeg) -> Result<Self, SpaceErr> {
1168 if (self.has_filesystem() && segment.is_filesystem_seg()) || segment.kind().is_mesh_seg() {
1169 let mut point = self.clone();
1170 point.segments.push(segment);
1171 Ok(point)
1172 } else {
1173 if self.has_filesystem() {
1174 Err("cannot push a Mesh segment onto a point after the FileSystemRoot segment has been pushed".into())
1175 } else {
1176 Err("cannot push a FileSystem segment onto a point until after the FileSystemRoot segment has been pushed".into())
1177 }
1178 }
1179 }
1180
1181 pub fn filepath(&self) -> Option<String> {
1182 let mut path = String::new();
1183 for segment in &self.segments {
1184 match segment {
1185 PointSeg::FilesystemRootDir => {
1186 path.push_str("/");
1187 }
1188 PointSeg::Dir(dir) => {
1189 path.push_str(dir.as_str());
1190 }
1191 PointSeg::File(file) => {
1192 path.push_str(file.as_str());
1193 }
1194 _ => {}
1195 }
1196 }
1197 if path.is_empty() {
1198 None
1199 } else {
1200 Some(path)
1201 }
1202 }
1203
1204 pub fn is_filesystem_ref(&self) -> bool {
1205 if let Option::Some(last_segment) = self.last_segment() {
1206 last_segment.is_filesystem_seg()
1207 } else {
1208 false
1209 }
1210 }
1211
1212 pub fn truncate(self, kind: PointSegKind) -> Result<Point, SpaceErr> {
1213 let mut segments = vec![];
1214 for segment in &self.segments {
1215 segments.push(segment.clone());
1216 if segment.kind() == kind {
1217 return Ok(Self {
1218 route: self.route,
1219 segments,
1220 });
1221 }
1222 }
1223
1224 Err(SpaceErr::Status {
1225 status: 404,
1226 message: format!(
1227 "Point segment kind: {} not found in point: {}",
1228 kind.to_string(),
1229 self.to_string()
1230 ),
1231 })
1232 }
1233}
1234
1235impl FromStr for Point {
1236 type Err = SpaceErr;
1237
1238 fn from_str(s: &str) -> Result<Self, Self::Err> {
1239 consume_point(s)
1240 }
1241}
1242
1243impl FromStr for PointVar {
1244 type Err = SpaceErr;
1245
1246 fn from_str(s: &str) -> Result<Self, Self::Err> {
1247 result(point_var(new_span(s)))
1248 }
1249}
1250
1251impl FromStr for PointCtx {
1252 type Err = SpaceErr;
1253
1254 fn from_str(s: &str) -> Result<Self, Self::Err> {
1255 Ok(result(point_var(new_span(s)))?.collapse()?)
1256 }
1257}
1258
1259impl Into<String> for Point {
1260 fn into(self) -> String {
1261 self.to_string()
1262 }
1263}
1264
1265impl<Route, Seg> PointDef<Route, Seg>
1266where
1267 Route: ToString,
1268 Seg: PointSegQuery + ToString,
1269{
1270 pub fn to_string_impl(&self, show_route: bool) -> String {
1271 let mut rtn = String::new();
1272
1273 if show_route {
1274 rtn.push_str(self.route.to_string().as_str());
1275 rtn.push_str("::");
1276 }
1277
1278 let mut post_fileroot = false;
1279
1280 if self.segments.is_empty() {
1281 rtn.push_str("ROOT");
1282 rtn.to_string()
1283 } else {
1284 for (i, segment) in self.segments.iter().enumerate() {
1285 if segment.is_filesystem_root() {
1286 post_fileroot = true;
1287 }
1288 if i > 0 {
1289 rtn.push_str(segment.kind().preceding_delim(post_fileroot));
1290 }
1291 rtn.push_str(segment.to_string().as_str());
1292 }
1293 rtn.to_string()
1294 }
1295 }
1296
1297 pub fn postfix(&self) -> String {
1298 self.to_string_impl(false)
1299 }
1300}
1301
1302impl<Route, Seg> ToString for PointDef<Route, Seg>
1303where
1304 Route: RouteSegQuery + ToString,
1305 Seg: PointSegQuery + ToString,
1306{
1307 fn to_string(&self) -> String {
1308 self.to_string_impl(!self.route.is_local())
1309 }
1310}
1311
1312impl Point {
1313 pub fn root() -> Self {
1314 Self {
1315 route: RouteSeg::This,
1316 segments: vec![],
1317 }
1318 }
1319
1320 pub fn root_with_route(route: RouteSeg) -> Self {
1321 Self {
1322 route,
1323 segments: vec![],
1324 }
1325 }
1326
1327 pub fn is_local_root(&self) -> bool {
1328 self.segments.is_empty() && self.route.is_local()
1329 }
1330}
1331
1332impl PointVar {
1333 pub fn is_dir(&self) -> bool {
1334 self.segments
1335 .last()
1336 .unwrap_or(&PointSegVar::Root)
1337 .kind()
1338 .is_dir()
1339 }
1340}
1341
1342impl PointCtx {
1343 pub fn is_dir(&self) -> bool {
1344 self.segments
1345 .last()
1346 .unwrap_or(&PointSegCtx::Root)
1347 .kind()
1348 .is_dir()
1349 }
1350}
1351
1352pub type Point = PointDef<RouteSeg, PointSeg>;
1365
1366pub type PointCtx = PointDef<RouteSeg, PointSegCtx>;
1380
1381pub type PointVar = PointDef<RouteSegVar, PointSegVar>;