opening_hours_syntax/rules/
mod.rs1pub mod day;
2pub mod time;
3
4use alloc::str::FromStr;
5use alloc::string::String;
6use alloc::sync::Arc;
7use alloc::vec::Vec;
8use core::fmt::Display;
9
10use crate::normalize::{canonical_to_ruleseq, drain_ruleseq_into_canonical};
11
12#[derive(Clone, Debug, Hash, PartialEq, Eq)]
15pub struct OpeningHoursExpression {
16 pub rules: Vec<RuleSequence>,
17}
18
19impl OpeningHoursExpression {
20 pub fn is_constant(&self) -> bool {
34 let Some(state) = self.rules.last().map(|rs| rs.as_state()) else {
35 return true;
36 };
37
38 let search_tail_full = self.rules.iter().rev().find(|rs| {
40 rs.day_selector.is_empty() || !rs.time_selector.is_00_24() || rs.as_state() != state
41 });
42
43 let Some(tail) = search_tail_full else {
44 return state == Default::default();
45 };
46
47 tail.as_state() == state && tail.is_constant()
48 }
49
50 #[doc = include_str!("../../doc/normalize.md")]
59 pub fn normalize(self) -> Self {
60 let mut old_rules = self.rules.into();
61 let canonical = drain_ruleseq_into_canonical(&mut old_rules);
62 let mut new_rules = canonical_to_ruleseq(canonical);
63 new_rules.extend(old_rules);
64 Self { rules: new_rules }
65 }
66}
67
68impl Display for OpeningHoursExpression {
69 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70 let Some(first) = self.rules.first() else {
71 return write!(f, "closed");
72 };
73
74 write!(f, "{first}")?;
75
76 for rule in &self.rules[1..] {
77 let separator = match rule.operator {
78 RuleOperator::Normal => "; ",
79 RuleOperator::Additional => ", ",
80 RuleOperator::Fallback => " || ",
81 };
82
83 write!(f, "{separator}")?;
84
85 rule.display(f, rule.operator == RuleOperator::Additional)?;
90 }
91
92 Ok(())
93 }
94}
95
96#[derive(Clone, Debug, Hash, PartialEq, Eq)]
99pub struct RuleSequence {
100 pub day_selector: day::DaySelector,
101 pub time_selector: time::TimeSelector,
102 pub kind: RuleKind,
103 pub operator: RuleOperator,
104 pub comment: Arc<str>,
105}
106
107impl RuleSequence {
108 pub fn is_constant(&self) -> bool {
111 self.day_selector.is_empty() && self.time_selector.is_00_24()
112 }
113
114 pub fn as_state(&self) -> (RuleKind, &str) {
117 (self.kind, &self.comment)
118 }
119
120 pub(crate) fn display(
125 &self,
126 f: &mut core::fmt::Formatter<'_>,
127 force_day_selector: bool,
128 ) -> core::fmt::Result {
129 let mut is_empty;
130
131 if self.is_constant() {
132 is_empty = false;
133 write!(f, "24/7")?;
134 } else {
135 self.day_selector.display(f, force_day_selector)?;
136 is_empty = !force_day_selector && self.day_selector.is_empty();
137
138 if !self.time_selector.is_00_24() {
139 if !is_empty {
140 write!(f, " ")?;
141 }
142
143 is_empty = is_empty && self.time_selector.is_00_24();
144 write!(f, "{}", self.time_selector)?;
145 }
146 }
147
148 if self.kind != RuleKind::Open {
149 if !is_empty {
150 write!(f, " ")?;
151 }
152
153 is_empty = false;
154 write!(f, "{}", self.kind)?;
155 }
156
157 if !self.comment.is_empty() {
158 if !is_empty {
159 write!(f, " ")?;
160 }
161
162 write!(f, "\"{}\"", self.comment)?;
163 }
164
165 Ok(())
166 }
167}
168
169impl Display for RuleSequence {
170 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
171 self.display(f, false)
172 }
173}
174
175#[derive(Copy, Clone, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)]
178pub enum RuleKind {
179 Open,
180 #[default]
181 Closed,
182 Unknown,
183}
184
185impl RuleKind {
186 pub const fn as_str(self) -> &'static str {
187 match self {
188 Self::Open => "open",
189 Self::Closed => "closed",
190 Self::Unknown => "unknown",
191 }
192 }
193}
194
195impl FromStr for RuleKind {
196 type Err = String;
197
198 fn from_str(s: &str) -> Result<Self, Self::Err> {
199 match s.to_lowercase().as_str() {
200 "open" => Ok(Self::Open),
201 "closed" => Ok(Self::Closed),
202 "unknown" => Ok(Self::Unknown),
203 other => Err(format!("Unknown rule kind {other:?}")),
204 }
205 }
206}
207
208impl Display for RuleKind {
209 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210 write!(f, "{}", self.as_str())
211 }
212}
213
214#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
217pub enum RuleOperator {
218 Normal,
219 Additional,
220 Fallback,
221}