pub mod day;
pub mod time;
use alloc::str::FromStr;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt::Display;
use crate::normalize::{canonical_to_ruleseq, drain_ruleseq_into_canonical};
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct OpeningHoursExpression {
pub rules: Vec<RuleSequence>,
}
impl OpeningHoursExpression {
pub fn is_constant(&self) -> bool {
let Some(state) = self.rules.last().map(|rs| rs.as_state()) else {
return true;
};
let search_tail_full = self.rules.iter().rev().find(|rs| {
rs.day_selector.is_empty() || !rs.time_selector.is_00_24() || rs.as_state() != state
});
let Some(tail) = search_tail_full else {
return state == Default::default();
};
tail.as_state() == state && tail.is_constant()
}
#[doc = include_str!("../../doc/normalize.md")]
pub fn normalize(self) -> Self {
let mut old_rules = self.rules.into();
let canonical = drain_ruleseq_into_canonical(&mut old_rules);
let mut new_rules = canonical_to_ruleseq(canonical);
new_rules.extend(old_rules);
Self { rules: new_rules }
}
}
impl Display for OpeningHoursExpression {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let Some(first) = self.rules.first() else {
return write!(f, "closed");
};
write!(f, "{first}")?;
for rule in &self.rules[1..] {
let separator = match rule.operator {
RuleOperator::Normal => "; ",
RuleOperator::Additional => ", ",
RuleOperator::Fallback => " || ",
};
write!(f, "{separator}")?;
rule.display(f, rule.operator == RuleOperator::Additional)?;
}
Ok(())
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct RuleSequence {
pub day_selector: day::DaySelector,
pub time_selector: time::TimeSelector,
pub kind: RuleKind,
pub operator: RuleOperator,
pub comment: Arc<str>,
}
impl RuleSequence {
pub fn is_constant(&self) -> bool {
self.day_selector.is_empty() && self.time_selector.is_00_24()
}
pub fn as_state(&self) -> (RuleKind, &str) {
(self.kind, &self.comment)
}
pub(crate) fn display(
&self,
f: &mut core::fmt::Formatter<'_>,
force_day_selector: bool,
) -> core::fmt::Result {
let mut is_empty;
if self.is_constant() {
is_empty = false;
write!(f, "24/7")?;
} else {
self.day_selector.display(f, force_day_selector)?;
is_empty = !force_day_selector && self.day_selector.is_empty();
if !self.time_selector.is_00_24() {
if !is_empty {
write!(f, " ")?;
}
is_empty = is_empty && self.time_selector.is_00_24();
write!(f, "{}", self.time_selector)?;
}
}
if self.kind != RuleKind::Open {
if !is_empty {
write!(f, " ")?;
}
is_empty = false;
write!(f, "{}", self.kind)?;
}
if !self.comment.is_empty() {
if !is_empty {
write!(f, " ")?;
}
write!(f, "\"{}\"", self.comment)?;
}
Ok(())
}
}
impl Display for RuleSequence {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.display(f, false)
}
}
#[derive(Copy, Clone, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub enum RuleKind {
Open,
#[default]
Closed,
Unknown,
}
impl RuleKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::Open => "open",
Self::Closed => "closed",
Self::Unknown => "unknown",
}
}
}
impl FromStr for RuleKind {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"open" => Ok(Self::Open),
"closed" => Ok(Self::Closed),
"unknown" => Ok(Self::Unknown),
other => Err(format!("Unknown rule kind {other:?}")),
}
}
}
impl Display for RuleKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum RuleOperator {
Normal,
Additional,
Fallback,
}