cosmic_space/
point.rs

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                                //                                    rtn.push_str("/");
705                            } 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                        //rtn.push_str("/");
743                    } else {
744                        rtn.push_str(":");
745                    }
746                }
747                rtn.push_str(segment.to_string().as_str());
748            }
749        }
750        if self.is_dir() {
751            //rtn.push_str("/");
752        }
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                    //segments.push(PointSeg::Root)
840                }
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
1352/// A Point is an address usually referencing a Particle.
1353/// Points can be created from a String composed of ':' delimited segments: `space.com:base:etc`
1354/// To create a Point:
1355/// ```
1356/// use std::str::FromStr;
1357/// use cosmic_space::loc::Point;
1358/// let Point = Point::from_str("my-domain.com:apps:my-app")?;
1359/// ```
1360/// Besides PointSegs points also have a RouteSeg which can change the meaning of a Point drastically
1361/// including referencing a completely different Cosmos, etc.  Routes are prepended and delimited by
1362/// a `::` i.e. `GLOBAL::executor:service`
1363///
1364pub type Point = PointDef<RouteSeg, PointSeg>;
1365
1366/// A Point with potential contextual information for example one with a working dir:
1367/// `.:mechtrons:mechtron`  the single `.` works the same as in unix shell and refers to the `working`
1368/// location.  You can also reference parent hierarchies just as you would expect: `..:another-app:something`
1369///
1370/// In order to create an absolute Point from a PointCtx one must call the PointCtx::to_resolved(&env) method
1371/// with a proper Env (environment) reference which should have a contextual point set:
1372/// ```
1373/// use std::str::FromStr;
1374/// use cosmic_space::loc::Point;
1375/// use cosmic_space::loc::PointCtx;
1376/// let point_var = PointCtx::from_str("..:another-app:something")?;
1377/// let point: Point = point_ctx.to_resolve(&env)?;
1378/// ```
1379pub type PointCtx = PointDef<RouteSeg, PointSegCtx>;
1380
1381/// A Point with potential Variables and Context (see PointCtx)
1382/// this point may look like `my-domain:users:${user}` in which case before it can be made into a
1383/// usable point it must be resolved like so:
1384/// ```
1385/// use std::str::FromStr;
1386/// use cosmic_space::loc::{Point, PointVar};
1387/// let point_var = PointVar::from_str("my-domain:users:${user}")?;
1388/// let point: Point = point_var.to_resolve(&env)?;
1389/// ```
1390pub type PointVar = PointDef<RouteSegVar, PointSegVar>;