assemble_core/
identifier.rs

1//! Identifiers are used by lazy_evaluation, tasks, and projects.
2
3use crate::lazy_evaluation::{Prop, VecProp};
4use crate::prelude::ProjectResult;
5use crate::project::buildable::Buildable;
6
7use crate::Project;
8use itertools::Itertools;
9use once_cell::sync::Lazy;
10use regex::Regex;
11use serde::de::Error as _;
12use serde::{Deserialize, Deserializer, Serialize, Serializer};
13use std::collections::{HashSet, VecDeque};
14use std::error::Error;
15
16use crate::project::finder::TaskFinder;
17use std::fmt::{Debug, Display, Formatter};
18use std::ops::Deref;
19use std::path::{Path, PathBuf};
20use std::str::FromStr;
21
22/// The separator between parts of an identifier
23pub const ID_SEPARATOR: char = ':';
24
25/// Represents some identifier in an assemble project.
26///
27/// Acts like a path. Consists for two parts, the `this` part and the `parent`. For example, in
28/// `root:inner:task`, the `this` is `task` and the `parent` is `root:inner`.
29#[derive(Default, Clone, Eq, PartialEq, Hash)]
30pub struct Id {
31    parent: Option<Box<Id>>,
32    this: String,
33}
34
35impl Serialize for Id {
36    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
37    where
38        S: Serializer,
39    {
40        self.iter().collect::<Vec<_>>().serialize(serializer)
41    }
42}
43
44impl<'de> Deserialize<'de> for Id {
45    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
46    where
47        D: Deserializer<'de>,
48    {
49        let vector: Vec<&str> = Vec::<&str>::deserialize(deserializer)?;
50        Id::from_iter(vector).map_err(|e| D::Error::custom(e.to_string()))
51    }
52}
53
54impl Display for Id {
55    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
56        if let Some(parent) = self.parent.as_deref() {
57            write!(f, "{}{ID_SEPARATOR}{}", parent, self.this)
58        } else {
59            write!(f, "{ID_SEPARATOR}{}", self.this)
60        }
61    }
62}
63
64impl Debug for Id {
65    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
66        write!(f, "\"{}\"", self)
67    }
68}
69
70impl Id {
71    /// Create a new id. The leading `:` is optional.
72    ///
73    /// # Error
74    /// Errors if it isn't a valid identifier.
75    ///
76    /// # Example
77    /// ```
78    /// # use assemble_core::identifier::Id;
79    /// let id = Id::new("root:inner:task").unwrap();
80    /// assert!(Id::new("&task").is_err());
81    /// assert!(Id::new("2132").is_err());
82    /// assert!(Id::new("gef::as").is_err());
83    /// assert_eq!(Id::new(":root"), Id::new("root"));
84    /// ```
85    pub fn new<S: AsRef<str>>(val: S) -> Result<Self, InvalidId> {
86        let mut as_str = val.as_ref();
87        if as_str.starts_with(":") {
88            as_str = &as_str[1..];
89        }
90        let split = as_str.split(ID_SEPARATOR);
91        Self::from_iter(split)
92    }
93
94    /// Create a new id that can't be checked
95    pub(crate) fn new_uncheckable<S: AsRef<str>>(val: S) -> Self {
96        let as_str = val.as_ref();
97        let split = as_str.split(ID_SEPARATOR);
98        Self::from_iter(split).unwrap()
99    }
100
101    /// Try to create an Id from an iterator of parts. Each part must be a valid **part** of an identifier.
102    ///
103    /// # Example
104    /// ```
105    /// # use assemble_core::identifier::Id;
106    /// assert!(Id::from_iter(["root", "task"]).is_ok());
107    /// assert!(Id::from_iter(["root:inner", "task"]).is_err());
108    /// assert!(Id::from_iter(["root", "inner", "task"]).is_ok());
109    /// ```
110    pub fn from_iter<S: AsRef<str>>(iter: impl IntoIterator<Item = S>) -> Result<Self, InvalidId> {
111        let mut iterator = iter.into_iter();
112        let start = iterator
113            .next()
114            .ok_or(InvalidId::new(""))
115            .and_then(|u| Self::new_unit(u.as_ref()))?;
116
117        iterator.try_fold(start, |accum, obj| {
118            let next_id = Self::new_unit(obj.as_ref())?;
119            Ok(accum.concat(next_id))
120        })
121    }
122
123    fn new_unit(id: &str) -> Result<Self, InvalidId> {
124        is_valid_identifier(id).map(|_| Id {
125            parent: None,
126            this: id.to_string(),
127        })
128    }
129
130    /// Joins something that can be turned into an identifier to the end of this Id.
131    ///
132    /// # Error
133    /// Errors if the next is not a valid identifier
134    pub fn join<S: AsRef<str>>(&self, next: S) -> Result<Self, InvalidId> {
135        Id::new(next).map(|id| self.clone().concat(id))
136    }
137
138    /// Concatenate two Id's together
139    pub fn concat<I: Into<Self>>(&self, mut other: I) -> Self {
140        let mut other: Self = other.into();
141        other.insert_as_topmost(self.clone());
142        other
143    }
144
145    fn insert_as_topmost(&mut self, parent: Self) {
146        match &mut self.parent {
147            Some(p) => p.insert_as_topmost(parent),
148            missing => *missing = Some(Box::new(parent)),
149        }
150    }
151
152    /// Returns this part of an identifier path.
153    pub fn this(&self) -> &str {
154        &self.this
155    }
156
157    /// Returns this part of an identifier path as an [`Id`](Id)
158    pub fn this_id(&self) -> Self {
159        Id::new_uncheckable(self.this())
160    }
161
162    /// Returns the parent identifier of this id, if it exists.
163    pub fn parent(&self) -> Option<&Id> {
164        self.parent.as_ref().map(|boxed| boxed.as_ref())
165    }
166
167    /// Check if the given representation is a valid shorthand.
168    pub fn is_shorthand(&self, repr: &str) -> bool {
169        let mut shorthands = repr.split(ID_SEPARATOR).rev();
170        for ancestor in self.ancestors() {
171            if let Some(shorthand) = shorthands.next() {
172                if !ancestor.is_shorthand_this(shorthand) {
173                    return false;
174                }
175            } else {
176                return false;
177            }
178        }
179        true
180    }
181
182    pub fn is_shorthand_this(&self, repr: &str) -> bool {
183        self.this() == repr
184    }
185
186    /// Gets the ancestors of this id.
187    ///
188    /// For example, the ancestors of `root:inner:task` would be
189    /// - `root:inner:task`
190    /// - `root:inner`
191    /// - `root`
192    pub fn ancestors(&self) -> impl Iterator<Item = &Id> {
193        let mut vec_dequeue = VecDeque::new();
194        vec_dequeue.push_front(self);
195        let mut ptr = self;
196        while let Some(parent) = ptr.parent.as_ref() {
197            vec_dequeue.push_back(parent);
198            ptr = parent;
199        }
200
201        vec_dequeue.into_iter()
202    }
203
204    pub fn iter(&self) -> Iter {
205        Iter::new(self)
206    }
207
208    pub fn as_path(&self) -> PathBuf {
209        PathBuf::from_iter(self.iter())
210    }
211}
212
213impl From<&str> for Id {
214    fn from(id: &str) -> Self {
215        Id::new(id).expect("invalid id")
216    }
217}
218
219impl<S: AsRef<str> + ?Sized> PartialEq<S> for Id {
220    fn eq(&self, other: &S) -> bool {
221        Id::new(other).map(|id| self == &id).unwrap_or(false)
222    }
223}
224
225impl PartialEq<str> for &Id {
226    fn eq(&self, other: &str) -> bool {
227        Id::new(other).map(|id| *self == &id).unwrap_or(false)
228    }
229}
230
231impl PartialEq<&Id> for Id {
232    fn eq(&self, other: &&Id) -> bool {
233        self == *other
234    }
235}
236
237impl PartialEq<Id> for &Id {
238    fn eq(&self, other: &Id) -> bool {
239        *self == other
240    }
241}
242
243/// How tasks are referenced throughout projects.
244///
245/// All tasks **must** have an associated TaskId.
246#[derive(Default, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
247pub struct TaskId(Id);
248
249impl TaskId {
250    pub fn new<S: AsRef<str>>(s: S) -> Result<TaskId, InvalidId> {
251        Id::new(s).map(Self)
252    }
253
254    /// Creates a new empty property. Does not register said property
255    pub fn prop<T: Clone + Send + Sync + 'static>(&self, name: &str) -> Result<Prop<T>, InvalidId> {
256        let id = self.join(name)?;
257        Ok(Prop::new(id))
258    }
259
260    /// Creates a new vec property. Does not register said property
261    pub fn vec_prop<T: Clone + Send + Sync + 'static>(
262        &self,
263        name: &str,
264    ) -> Result<VecProp<T>, InvalidId> {
265        let id = self.join(name)?;
266        Ok(VecProp::new(id))
267    }
268
269    /// Gets the project id that contains this task.
270    pub fn project_id(&self) -> Option<ProjectId> {
271        self.parent().map(|id| ProjectId(id.clone()))
272    }
273}
274
275impl Debug for TaskId {
276    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
277        write!(f, "{:?}", self.0)
278    }
279}
280
281impl Buildable for TaskId {
282    fn get_dependencies(&self, _project: &Project) -> ProjectResult<HashSet<TaskId>> {
283        // println!("Attempting to get dependencies for {} in {}", self, project);
284        // let info = project
285        //     .task_container()
286        //     .get_task(self)?;
287        // println!("got info: {:#?}", info.task_id());
288        let mut output: HashSet<TaskId> = HashSet::new();
289        output.insert(self.clone());
290        Ok(output)
291    }
292}
293
294impl Buildable for &str {
295    fn get_dependencies(&self, project: &Project) -> ProjectResult<HashSet<TaskId>> {
296        let task_id: Box<dyn Buildable> = todo!();
297        task_id.get_dependencies(project)
298    }
299}
300
301impl Deref for TaskId {
302    type Target = Id;
303
304    fn deref(&self) -> &Self::Target {
305        &self.0
306    }
307}
308
309impl AsRef<TaskId> for TaskId {
310    fn as_ref(&self) -> &TaskId {
311        self
312    }
313}
314
315impl From<&TaskId> for TaskId {
316    fn from(t: &TaskId) -> Self {
317        t.clone()
318    }
319}
320
321impl TryFrom<&str> for TaskId {
322    type Error = InvalidId;
323
324    fn try_from(value: &str) -> Result<Self, Self::Error> {
325        Self::new(value)
326    }
327}
328
329impl TryFrom<String> for TaskId {
330    type Error = InvalidId;
331
332    fn try_from(value: String) -> Result<Self, Self::Error> {
333        Self::new(value)
334    }
335}
336
337impl From<Id> for TaskId {
338    fn from(i: Id) -> Self {
339        Self(i)
340    }
341}
342
343/// How projects are referenced. Unlike tasks, projects don't have to have parents.
344#[derive(Default, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
345pub struct ProjectId(Id);
346
347impl ProjectId {
348    pub fn root() -> Self {
349        Self(Id::new("root").unwrap())
350    }
351
352    pub fn from_path(path: impl AsRef<Path>) -> Result<Self, InvalidId> {
353        let mut path = path.as_ref();
354        if let Ok(prefixless) = path.strip_prefix("/") {
355            path = prefixless;
356        }
357        let iter = path
358            .iter()
359            .map(|s| s.to_str().ok_or(InvalidId::new(&path.to_string_lossy())))
360            .collect::<Result<Vec<_>, _>>()?;
361        Id::from_iter(iter).map(Self)
362    }
363
364    pub fn new(id: &str) -> Result<Self, InvalidId> {
365        let name = Id::new(id)?;
366        Ok(ProjectId(name))
367    }
368}
369
370impl Debug for ProjectId {
371    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
372        write!(f, "{:?}", self.0)
373    }
374}
375
376impl TryFrom<&Path> for ProjectId {
377    type Error = InvalidId;
378
379    fn try_from(value: &Path) -> Result<Self, Self::Error> {
380        Self::from_path(value)
381    }
382}
383
384impl TryFrom<&str> for ProjectId {
385    type Error = InvalidId;
386
387    fn try_from(value: &str) -> Result<Self, Self::Error> {
388        Self::new(value)
389    }
390}
391
392impl From<Id> for ProjectId {
393    fn from(id: Id) -> Self {
394        Self(id)
395    }
396}
397
398impl From<ProjectId> for Id {
399    fn from(id: ProjectId) -> Self {
400        id.0
401    }
402}
403
404impl From<&ProjectId> for ProjectId {
405    fn from(value: &ProjectId) -> Self {
406        value.clone()
407    }
408}
409
410impl Deref for ProjectId {
411    type Target = Id;
412
413    fn deref(&self) -> &Self::Target {
414        &self.0
415    }
416}
417
418macro_rules! deref_to_id {
419    ($ty:ty) => {
420        impl<I: ?Sized> PartialEq<I> for $ty
421        where
422            Id: PartialEq<I>,
423        {
424            fn eq(&self, other: &I) -> bool {
425                self.deref().eq(other)
426            }
427        }
428
429        impl Display for $ty {
430            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
431                write!(f, "{}", self.deref())
432            }
433        }
434
435        impl FromStr for $ty {
436            type Err = InvalidId;
437
438            /// Parses a task ID. Unlike the TryFrom methods, this one can produced multi level ids
439            fn from_str(s: &str) -> Result<Self, Self::Err> {
440                let mut output: Option<Id> = None;
441                if !s.starts_with(':') {
442                    return Err(InvalidId::new(s));
443                }
444
445                for task_part in s[1..].split(":") {
446                    match output {
447                        Some(old_output) => output = Some(old_output.join(task_part)?),
448                        None => output = Some(Id::new(task_part)?),
449                    }
450                }
451                output
452                    .map(|id| <$ty>::from(id))
453                    .ok_or(InvalidId(s.to_string()))
454            }
455        }
456    };
457}
458
459deref_to_id!(TaskId);
460deref_to_id!(ProjectId);
461
462/// Create new tasks Ids
463#[derive(Clone, Debug)]
464pub struct TaskIdFactory {
465    project: ProjectId,
466}
467
468impl TaskIdFactory {
469    pub(crate) fn new(project: ProjectId) -> Self {
470        Self { project }
471    }
472
473    pub fn create(&self, task_name: impl AsRef<str>) -> Result<TaskId, InvalidId> {
474        self.project.join(task_name).map(TaskId)
475    }
476}
477
478#[derive(Debug, Eq, PartialEq)]
479pub struct InvalidId(pub String);
480
481impl InvalidId {
482    pub fn new(string: impl AsRef<str>) -> Self {
483        Self(string.as_ref().to_string())
484    }
485}
486
487impl Display for InvalidId {
488    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
489        write!(f, "Invalid Identifier {:?}", self.0)
490    }
491}
492
493impl Error for InvalidId {}
494
495pub fn is_valid_identifier(id: &str) -> Result<(), InvalidId> {
496    static VALID_ID_PATTERN: Lazy<Regex> =
497        Lazy::new(|| Regex::new(r"[a-zA-Z][\w-]*").expect("Invalid Pattern"));
498
499    VALID_ID_PATTERN
500        .find(id)
501        .ok_or(InvalidId::new(id))
502        .and_then(|mat| {
503            if mat.as_str() == id {
504                Ok(())
505            } else {
506                Err(InvalidId::new(id))
507            }
508        })
509}
510
511pub struct Iter<'id> {
512    ids: Vec<&'id Id>,
513}
514
515impl<'i> Iter<'i> {
516    fn new(id: &'i Id) -> Self {
517        let ancestors = id.ancestors();
518        let vec = Vec::from_iter(ancestors);
519        Self { ids: vec }
520    }
521}
522
523impl<'i> Iterator for Iter<'i> {
524    type Item = &'i str;
525
526    fn next(&mut self) -> Option<Self::Item> {
527        let top = self.ids.pop()?;
528        Some(top.this())
529    }
530}
531
532#[cfg(test)]
533mod tests {
534    use crate::identifier::Id;
535
536    #[test]
537    fn from_string() {
538        let _id = Id::from_iter(&["project", "task"]).unwrap();
539        let _other_id = Id::new("project:task");
540    }
541
542    #[test]
543    fn to_string() {
544        let id = Id::from_iter(&["project", "task"]).unwrap();
545        assert_eq!(id.to_string(), ":project:task");
546
547        let id = Id::from_iter(&["task"]).unwrap();
548        assert_eq!(id.to_string(), ":task");
549    }
550
551    #[test]
552    fn ancestors() {
553        let id = Id::new_uncheckable("root:child:task");
554        let mut ancestors = id.ancestors();
555        assert_eq!(
556            ancestors.next(),
557            Some("root:child:task").map(Id::new_uncheckable).as_ref()
558        );
559        assert_eq!(
560            ancestors.next(),
561            Some("root:child").map(Id::new_uncheckable).as_ref()
562        );
563        assert_eq!(
564            ancestors.next(),
565            Some("root").map(Id::new_uncheckable).as_ref()
566        );
567        assert_eq!(ancestors.next(), None);
568    }
569
570    #[test]
571    fn is_shorthand() {
572        let id = Id::from_iter(&["project", "task"]).unwrap();
573        let shorthand = "project:task";
574        assert!(id.is_shorthand(shorthand));
575    }
576}