use std::{fmt, sync::OnceLock};
use regex::bytes::Regex;
use time::{Date, format_description::FormatItem, macros::format_description};
use crate::StringTyped;
#[must_use]
pub fn is_valid(facet: &str) -> bool {
facet.trim() == facet && facet.as_bytes().first() != Some(&b'/')
}
#[must_use]
pub fn is_empty(facet: &str) -> bool {
debug_assert!(is_valid(facet));
facet.is_empty()
}
#[must_use]
pub fn has_date_like_suffix(facet: &str) -> bool {
debug_assert!(is_valid(facet));
date_like_suffix_regex().is_match(facet.as_bytes())
}
#[must_use]
pub fn try_split_into_prefix_and_date_like_suffix(facet: &str) -> Option<(&str, &str)> {
debug_assert!(is_valid(facet));
if facet.len() < DATE_LIKE_SUFFIX_LEN {
return None;
}
let prefix_len = facet.len() - DATE_LIKE_SUFFIX_LEN;
let date_suffix = &facet[prefix_len..];
if !date_suffix.is_ascii() {
return None;
}
let prefix = &facet[..prefix_len];
(prefix, date_suffix).into()
}
#[must_use]
pub fn try_split_into_prefix_and_parse_date_suffix(facet: &str) -> Option<(&str, Option<Date>)> {
debug_assert!(is_valid(facet));
let (prefix, date_suffix) = try_split_into_prefix_and_date_like_suffix(facet)?;
let date = Date::parse(date_suffix, DATE_LIKE_SUFFIX_FORMAT).ok();
(prefix, date).into()
}
const DATE_LIKE_SUFFIX_FORMAT: &[FormatItem<'static>] = format_description!("@[year][month][day]");
const DATE_LIKE_SUFFIX_LEN: usize = 1 + 8;
const DATE_LIKE_SUFFIX_REGEX_STR: &str = r"(^|[^\s])@\d{8}$";
static DATE_LIKE_SUFFIX_REGEX: OnceLock<Regex> = OnceLock::new();
#[must_use]
fn date_like_suffix_regex() -> &'static Regex {
DATE_LIKE_SUFFIX_REGEX.get_or_init(|| DATE_LIKE_SUFFIX_REGEX_STR.parse().unwrap())
}
const INVALID_DATE_LIKE_SUFFIX_REGEX_STR: &str = r"[\s]+@\d{8}$";
static INVALID_DATE_LIKE_SUFFIX_REGEX: OnceLock<Regex> = OnceLock::new();
#[must_use]
fn invalid_date_like_suffix_regex() -> &'static Regex {
INVALID_DATE_LIKE_SUFFIX_REGEX
.get_or_init(|| INVALID_DATE_LIKE_SUFFIX_REGEX_STR.parse().unwrap())
}
#[must_use]
pub fn has_invalid_date_like_suffix(facet: &str) -> bool {
debug_assert!(is_valid(facet));
invalid_date_like_suffix_regex().is_match(facet.as_bytes())
}
fn format_date_like_suffix(date: Date) -> Result<String, time::error::Format> {
date.format(DATE_LIKE_SUFFIX_FORMAT)
}
pub trait Facet: StringTyped + Default + PartialEq + Ord {
fn from_prefix_with_date_suffix(prefix: &str, date: Date) -> Result<Self, time::error::Format> {
let suffix = format_date_like_suffix(date)?;
Ok(Self::from_format_args(format_args!("{prefix}{suffix}")))
}
fn from_prefix_args_with_date_suffix(
prefix_args: fmt::Arguments<'_>,
date: Date,
) -> Result<Self, time::error::Format> {
let suffix = format_date_like_suffix(date)?;
Ok(Self::from_format_args(format_args!(
"{prefix_args}{suffix}"
)))
}
#[must_use]
fn is_valid(&self) -> bool {
is_valid(self.as_ref())
}
#[must_use]
fn is_empty(&self) -> bool {
is_empty(self.as_ref())
}
#[must_use]
fn has_date_like_suffix(&self) -> bool {
has_date_like_suffix(self.as_ref())
}
#[must_use]
fn try_split_into_prefix_and_date_like_suffix(&self) -> Option<(&str, &str)> {
try_split_into_prefix_and_date_like_suffix(self.as_ref())
}
#[must_use]
fn try_split_into_prefix_and_parse_date_suffix(&self) -> Option<(&str, Option<Date>)> {
try_split_into_prefix_and_parse_date_suffix(self.as_ref())
}
}
impl<T> Facet for T where T: StringTyped + Default + PartialEq + Ord {}
#[cfg(test)]
mod tests;