use crate::lazy_evaluation::{Prop, VecProp};
use crate::prelude::ProjectResult;
use crate::project::buildable::Buildable;
use crate::Project;
use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::{HashSet, VecDeque};
use std::error::Error;
use crate::project::finder::TaskFinder;
use std::fmt::{Debug, Display, Formatter};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::str::FromStr;
pub const ID_SEPARATOR: char = ':';
#[derive(Default, Clone, Eq, PartialEq, Hash)]
pub struct Id {
parent: Option<Box<Id>>,
this: String,
}
impl Serialize for Id {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.iter().collect::<Vec<_>>().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Id {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let vector: Vec<&str> = Vec::<&str>::deserialize(deserializer)?;
Id::from_iter(vector).map_err(|e| D::Error::custom(e.to_string()))
}
}
impl Display for Id {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(parent) = self.parent.as_deref() {
write!(f, "{}{ID_SEPARATOR}{}", parent, self.this)
} else {
write!(f, "{ID_SEPARATOR}{}", self.this)
}
}
}
impl Debug for Id {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\"", self)
}
}
impl Id {
pub fn new<S: AsRef<str>>(val: S) -> Result<Self, InvalidId> {
let mut as_str = val.as_ref();
if as_str.starts_with(":") {
as_str = &as_str[1..];
}
let split = as_str.split(ID_SEPARATOR);
Self::from_iter(split)
}
pub(crate) fn new_uncheckable<S: AsRef<str>>(val: S) -> Self {
let as_str = val.as_ref();
let split = as_str.split(ID_SEPARATOR);
Self::from_iter(split).unwrap()
}
pub fn from_iter<S: AsRef<str>>(iter: impl IntoIterator<Item = S>) -> Result<Self, InvalidId> {
let mut iterator = iter.into_iter();
let start = iterator
.next()
.ok_or(InvalidId::new(""))
.and_then(|u| Self::new_unit(u.as_ref()))?;
iterator.try_fold(start, |accum, obj| {
let next_id = Self::new_unit(obj.as_ref())?;
Ok(accum.concat(next_id))
})
}
fn new_unit(id: &str) -> Result<Self, InvalidId> {
is_valid_identifier(id).map(|_| Id {
parent: None,
this: id.to_string(),
})
}
pub fn join<S: AsRef<str>>(&self, next: S) -> Result<Self, InvalidId> {
Id::new(next).map(|id| self.clone().concat(id))
}
pub fn concat<I: Into<Self>>(&self, mut other: I) -> Self {
let mut other: Self = other.into();
other.insert_as_topmost(self.clone());
other
}
fn insert_as_topmost(&mut self, parent: Self) {
match &mut self.parent {
Some(p) => p.insert_as_topmost(parent),
missing => *missing = Some(Box::new(parent)),
}
}
pub fn this(&self) -> &str {
&self.this
}
pub fn this_id(&self) -> Self {
Id::new_uncheckable(self.this())
}
pub fn parent(&self) -> Option<&Id> {
self.parent.as_ref().map(|boxed| boxed.as_ref())
}
pub fn is_shorthand(&self, repr: &str) -> bool {
let mut shorthands = repr.split(ID_SEPARATOR).rev();
for ancestor in self.ancestors() {
if let Some(shorthand) = shorthands.next() {
if !ancestor.is_shorthand_this(shorthand) {
return false;
}
} else {
return false;
}
}
true
}
pub fn is_shorthand_this(&self, repr: &str) -> bool {
self.this() == repr
}
pub fn ancestors(&self) -> impl Iterator<Item = &Id> {
let mut vec_dequeue = VecDeque::new();
vec_dequeue.push_front(self);
let mut ptr = self;
while let Some(parent) = ptr.parent.as_ref() {
vec_dequeue.push_back(parent);
ptr = parent;
}
vec_dequeue.into_iter()
}
pub fn iter(&self) -> Iter {
Iter::new(self)
}
pub fn as_path(&self) -> PathBuf {
PathBuf::from_iter(self.iter())
}
}
impl From<&str> for Id {
fn from(id: &str) -> Self {
Id::new(id).expect("invalid id")
}
}
impl<S: AsRef<str> + ?Sized> PartialEq<S> for Id {
fn eq(&self, other: &S) -> bool {
Id::new(other).map(|id| self == &id).unwrap_or(false)
}
}
impl PartialEq<str> for &Id {
fn eq(&self, other: &str) -> bool {
Id::new(other).map(|id| *self == &id).unwrap_or(false)
}
}
impl PartialEq<&Id> for Id {
fn eq(&self, other: &&Id) -> bool {
self == *other
}
}
impl PartialEq<Id> for &Id {
fn eq(&self, other: &Id) -> bool {
*self == other
}
}
#[derive(Default, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
pub struct TaskId(Id);
impl TaskId {
pub fn new<S: AsRef<str>>(s: S) -> Result<TaskId, InvalidId> {
Id::new(s).map(Self)
}
pub fn prop<T: Clone + Send + Sync + 'static>(&self, name: &str) -> Result<Prop<T>, InvalidId> {
let id = self.join(name)?;
Ok(Prop::new(id))
}
pub fn vec_prop<T: Clone + Send + Sync + 'static>(
&self,
name: &str,
) -> Result<VecProp<T>, InvalidId> {
let id = self.join(name)?;
Ok(VecProp::new(id))
}
pub fn project_id(&self) -> Option<ProjectId> {
self.parent().map(|id| ProjectId(id.clone()))
}
}
impl Debug for TaskId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl Buildable for TaskId {
fn get_dependencies(&self, _project: &Project) -> ProjectResult<HashSet<TaskId>> {
let mut output: HashSet<TaskId> = HashSet::new();
output.insert(self.clone());
Ok(output)
}
}
impl Buildable for &str {
fn get_dependencies(&self, project: &Project) -> ProjectResult<HashSet<TaskId>> {
let task_id: Box<dyn Buildable> = todo!();
task_id.get_dependencies(project)
}
}
impl Deref for TaskId {
type Target = Id;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<TaskId> for TaskId {
fn as_ref(&self) -> &TaskId {
self
}
}
impl From<&TaskId> for TaskId {
fn from(t: &TaskId) -> Self {
t.clone()
}
}
impl TryFrom<&str> for TaskId {
type Error = InvalidId;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<String> for TaskId {
type Error = InvalidId;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<Id> for TaskId {
fn from(i: Id) -> Self {
Self(i)
}
}
#[derive(Default, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
pub struct ProjectId(Id);
impl ProjectId {
pub fn root() -> Self {
Self(Id::new("root").unwrap())
}
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, InvalidId> {
let mut path = path.as_ref();
if let Ok(prefixless) = path.strip_prefix("/") {
path = prefixless;
}
let iter = path
.iter()
.map(|s| s.to_str().ok_or(InvalidId::new(&path.to_string_lossy())))
.collect::<Result<Vec<_>, _>>()?;
Id::from_iter(iter).map(Self)
}
pub fn new(id: &str) -> Result<Self, InvalidId> {
let name = Id::new(id)?;
Ok(ProjectId(name))
}
}
impl Debug for ProjectId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl TryFrom<&Path> for ProjectId {
type Error = InvalidId;
fn try_from(value: &Path) -> Result<Self, Self::Error> {
Self::from_path(value)
}
}
impl TryFrom<&str> for ProjectId {
type Error = InvalidId;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<Id> for ProjectId {
fn from(id: Id) -> Self {
Self(id)
}
}
impl From<ProjectId> for Id {
fn from(id: ProjectId) -> Self {
id.0
}
}
impl From<&ProjectId> for ProjectId {
fn from(value: &ProjectId) -> Self {
value.clone()
}
}
impl Deref for ProjectId {
type Target = Id;
fn deref(&self) -> &Self::Target {
&self.0
}
}
macro_rules! deref_to_id {
($ty:ty) => {
impl<I: ?Sized> PartialEq<I> for $ty
where
Id: PartialEq<I>,
{
fn eq(&self, other: &I) -> bool {
self.deref().eq(other)
}
}
impl Display for $ty {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.deref())
}
}
impl FromStr for $ty {
type Err = InvalidId;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut output: Option<Id> = None;
if !s.starts_with(':') {
return Err(InvalidId::new(s));
}
for task_part in s[1..].split(":") {
match output {
Some(old_output) => output = Some(old_output.join(task_part)?),
None => output = Some(Id::new(task_part)?),
}
}
output
.map(|id| <$ty>::from(id))
.ok_or(InvalidId(s.to_string()))
}
}
};
}
deref_to_id!(TaskId);
deref_to_id!(ProjectId);
#[derive(Clone, Debug)]
pub struct TaskIdFactory {
project: ProjectId,
}
impl TaskIdFactory {
pub(crate) fn new(project: ProjectId) -> Self {
Self { project }
}
pub fn create(&self, task_name: impl AsRef<str>) -> Result<TaskId, InvalidId> {
self.project.join(task_name).map(TaskId)
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct InvalidId(pub String);
impl InvalidId {
pub fn new(string: impl AsRef<str>) -> Self {
Self(string.as_ref().to_string())
}
}
impl Display for InvalidId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Invalid Identifier {:?}", self.0)
}
}
impl Error for InvalidId {}
pub fn is_valid_identifier(id: &str) -> Result<(), InvalidId> {
static VALID_ID_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new(r"[a-zA-Z][\w-]*").expect("Invalid Pattern"));
VALID_ID_PATTERN
.find(id)
.ok_or(InvalidId::new(id))
.and_then(|mat| {
if mat.as_str() == id {
Ok(())
} else {
Err(InvalidId::new(id))
}
})
}
pub struct Iter<'id> {
ids: Vec<&'id Id>,
}
impl<'i> Iter<'i> {
fn new(id: &'i Id) -> Self {
let ancestors = id.ancestors();
let vec = Vec::from_iter(ancestors);
Self { ids: vec }
}
}
impl<'i> Iterator for Iter<'i> {
type Item = &'i str;
fn next(&mut self) -> Option<Self::Item> {
let top = self.ids.pop()?;
Some(top.this())
}
}
#[cfg(test)]
mod tests {
use crate::identifier::Id;
#[test]
fn from_string() {
let _id = Id::from_iter(&["project", "task"]).unwrap();
let _other_id = Id::new("project:task");
}
#[test]
fn to_string() {
let id = Id::from_iter(&["project", "task"]).unwrap();
assert_eq!(id.to_string(), ":project:task");
let id = Id::from_iter(&["task"]).unwrap();
assert_eq!(id.to_string(), ":task");
}
#[test]
fn ancestors() {
let id = Id::new_uncheckable("root:child:task");
let mut ancestors = id.ancestors();
assert_eq!(
ancestors.next(),
Some("root:child:task").map(Id::new_uncheckable).as_ref()
);
assert_eq!(
ancestors.next(),
Some("root:child").map(Id::new_uncheckable).as_ref()
);
assert_eq!(
ancestors.next(),
Some("root").map(Id::new_uncheckable).as_ref()
);
assert_eq!(ancestors.next(), None);
}
#[test]
fn is_shorthand() {
let id = Id::from_iter(&["project", "task"]).unwrap();
let shorthand = "project:task";
assert!(id.is_shorthand(shorthand));
}
}