1use 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
22pub const ID_SEPARATOR: char = ':';
24
25#[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 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 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 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 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 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 pub fn this(&self) -> &str {
154 &self.this
155 }
156
157 pub fn this_id(&self) -> Self {
159 Id::new_uncheckable(self.this())
160 }
161
162 pub fn parent(&self) -> Option<&Id> {
164 self.parent.as_ref().map(|boxed| boxed.as_ref())
165 }
166
167 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 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#[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 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 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 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 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#[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 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#[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}