use crate::error;
use crate::syntax::{
SHAPE_ID_ABSOLUTE_SEPARATOR, SHAPE_ID_MEMBER_SEPARATOR, SHAPE_ID_NAMESPACE_SEPARATOR,
};
use regex::Regex;
use std::fmt::{Debug, Display, Formatter};
use std::hash::Hash;
use std::str::FromStr;
#[allow(clippy::derive_hash_xor_eq)]
#[derive(Clone, Debug, Eq, PartialOrd, Ord, Hash)]
pub struct Identifier(String);
#[allow(clippy::derive_hash_xor_eq)]
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug, Eq, PartialOrd, Ord, Hash)]
pub struct NamespaceID(String);
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ShapeID {
namespace: NamespaceID,
shape_name: Identifier,
member_name: Option<Identifier>,
}
pub trait HasIdentity<T>
where
T: Clone + Debug + Display + Eq + Ord + Hash,
{
fn id(&self) -> &T;
fn is(&self, id: &T) -> bool {
self.id() == id
}
fn set_id(&mut self, id: T);
}
lazy_static! {
static ref RE_IDENTIFIER: Regex = Regex::new(r"^_*[[:alpha:]][_[[:alnum:]]]*$").unwrap();
static ref RE_NAMESPACE: Regex =
Regex::new(r"^_*[[:alpha:]][_[[:alnum:]]]*(\._*[[:alpha:]][_[[:alnum:]]]*)*$").unwrap();
static ref RE_SHAPE_ID: Regex = Regex::new(
r"(?x)
^
(_*[[:alpha:]][_[[:alnum:]]]**(\._*[[:alpha:]][_[[:alnum:]]]*)*)\#
(_*[[:alpha:]][_[[:alnum:]]]*)
(\$(_*[[:alpha:]][_[[:alnum:]]]*))?
$"
)
.unwrap();
}
impl Display for Identifier {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for Identifier {
type Err = error::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if Self::is_valid(s) {
Ok(Self(s.to_string()))
} else {
Err(error::ErrorKind::InvalidShapeID(s.to_string()).into())
}
}
}
impl PartialEq for Identifier {
fn eq(&self, other: &Self) -> bool {
self.0.to_lowercase() == other.0.to_lowercase()
}
}
impl Identifier {
pub fn new_unchecked(s: &str) -> Self {
Self(s.to_string())
}
pub fn is_valid(s: &str) -> bool {
RE_IDENTIFIER.is_match(s)
}
}
impl Display for NamespaceID {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for NamespaceID {
type Err = error::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if Self::is_valid(s) {
Ok(Self(s.to_string()))
} else {
Err(error::ErrorKind::InvalidShapeID(s.to_string()).into())
}
}
}
impl PartialEq for NamespaceID {
fn eq(&self, other: &Self) -> bool {
self.0.to_lowercase() == other.0.to_lowercase()
}
}
impl NamespaceID {
pub fn new_unchecked(s: &str) -> Self {
Self(s.to_string())
}
pub fn is_valid(s: &str) -> bool {
for id in s.split(SHAPE_ID_NAMESPACE_SEPARATOR) {
if !Identifier::is_valid(id) {
return false;
}
}
true
}
pub fn make_shape(&self, shape_name: Identifier) -> ShapeID {
ShapeID::new(self.clone(), shape_name, None)
}
pub fn make_member(&self, shape_name: Identifier, member_name: Identifier) -> ShapeID {
ShapeID::new(self.clone(), shape_name, Some(member_name))
}
pub fn split(&self) -> impl Iterator<Item = Identifier> + '_ {
self.0
.split(SHAPE_ID_NAMESPACE_SEPARATOR)
.map(|s| Identifier::new_unchecked(s))
}
}
impl Display for ShapeID {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.namespace, SHAPE_ID_ABSOLUTE_SEPARATOR)?;
write!(f, "{}", self.shape_name)?;
if let Some(member_name) = &self.member_name {
write!(f, "{}{}", SHAPE_ID_MEMBER_SEPARATOR, member_name)?;
}
Ok(())
}
}
impl FromStr for ShapeID {
type Err = error::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(result) = RE_SHAPE_ID.captures(s) {
let namespace = NamespaceID(result.get(1).unwrap().as_str().to_string());
let shape_name = Identifier(result.get(3).unwrap().as_str().to_string());
let member_name = result.get(5).map(|v| Identifier(v.as_str().to_string()));
Ok(Self {
namespace,
shape_name,
member_name,
})
} else {
Err(error::ErrorKind::InvalidShapeID(s.to_string()).into())
}
}
}
impl ShapeID {
pub fn is_valid(s: &str) -> bool {
RE_SHAPE_ID.is_match(s)
}
pub fn new(
namespace: NamespaceID,
shape_name: Identifier,
member_name: Option<Identifier>,
) -> Self {
Self {
namespace,
shape_name,
member_name,
}
}
pub fn new_unchecked(namespace: &str, shape_name: &str, member_name: Option<&str>) -> Self {
Self::new(
NamespaceID::new_unchecked(namespace),
Identifier::new_unchecked(shape_name),
member_name.map(|member_name| Identifier::new_unchecked(member_name)),
)
}
pub fn shape(namespace: &str, shape_name: &str) -> Self {
Self::new(
namespace.parse().unwrap(),
shape_name.parse().unwrap(),
None,
)
}
pub fn member(namespace: &str, shape_name: &str, member_name: &str) -> Self {
Self::new(
namespace.parse().unwrap(),
shape_name.parse().unwrap(),
Some(member_name.parse().unwrap()),
)
}
pub fn shape_only(&self) -> ShapeID {
if self.is_member() {
Self::new(self.namespace.clone(), self.shape_name.clone(), None)
} else {
self.clone()
}
}
pub fn namespace(&self) -> &NamespaceID {
&self.namespace
}
pub fn shape_name(&self) -> &Identifier {
&self.shape_name
}
pub fn member_name(&self) -> &Option<Identifier> {
&self.member_name
}
pub fn is_member(&self) -> bool {
self.member_name.is_some()
}
pub fn is_in_same_namespace(&self, other: &Self) -> bool {
self.namespace == other.namespace
}
pub fn is_valid_member(&self, other: &Self) -> bool {
!self.is_member()
&& other.is_member()
&& self.is_in_same_namespace(other)
&& self.shape_name == other.shape_name
}
pub fn make_shape(&self, shape_name: Identifier) -> Self {
Self {
shape_name,
..self.clone()
}
}
pub fn make_member(&self, member_name: Identifier) -> Self {
Self {
member_name: Some(member_name),
..self.clone()
}
}
}