use crate::model::inline::HasInlineContent;
use regex::Regex;
#[cfg(feature = "fmt_json")]
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use std::str::FromStr;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "fmt_json", derive(Serialize, Deserialize))]
pub struct Label(String);
pub trait HasLabel {
fn has_label(&self) -> bool;
fn label(&self) -> &Option<Label>;
fn set_label(&mut self, label: Label) -> &mut Self;
fn unset_label(&mut self) -> &mut Self;
}
pub trait AutoLabel: HasLabel {
#[allow(unused_variables)]
fn auto_label(&mut self) -> &mut Self;
}
impl<T> AutoLabel for T
where
T: HasInlineContent + HasLabel,
{
fn auto_label(&mut self) -> &mut Self {
self.set_label(Label::safe_from(&self.unformatted_string(), None))
}
}
lazy_static! {
static ref RE_LABEL: Regex = Regex::new(r"^\p{L}+[\p{L}\p{N}_\-\.:]*$").unwrap();
}
impl Display for Label {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for Label {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if Self::is_valid(s) {
Ok(Self(s.to_string()))
} else {
Err(())
}
}
}
impl Deref for Label {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
inner_impl!(Label, String);
impl Label {
pub fn is_valid(label: &str) -> bool {
RE_LABEL.is_match(label)
}
pub fn generate(prefix: Option<&str>) -> Self {
let prefix = if let Some(prefix) = prefix {
assert!(!prefix.is_empty());
assert!(prefix.chars().all(char::is_alphabetic));
format!("{}:", prefix)
} else {
String::from("gen:")
};
Self(format!("{}{}", prefix, blob_uuid::random_blob()))
}
pub fn safe_from(label: &str, prefix: Option<&str>) -> Self {
assert!(!label.is_empty());
let mut characters = label.chars();
let first = characters.next().unwrap();
assert!(first.is_alphabetic());
let rest: String = characters
.map(|c| {
if c.is_alphanumeric() || c == '_' || c == '-' || c == '.' || c == ':' {
c
} else {
'_'
}
})
.collect();
let prefix = if let Some(prefix) = prefix {
assert!(prefix.chars().all(char::is_alphabetic));
format!("{}:", prefix)
} else {
String::new()
};
Self(format!("{}{}{}", prefix, first, rest))
}
pub fn copy_from(labeled: &impl HasLabel) -> Option<Self> {
match labeled.label() {
None => None,
Some(label) => Some(Self(label.inner().clone())),
}
}
}