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::frame::Bounded;
11use crate::normalize::paving::{Paving, Paving5D, UnpackFromBack};
12use crate::normalize::{canonical_to_seq, ruleseq_to_selector};
13
14#[derive(Clone, Debug, Hash, PartialEq, Eq)]
17pub struct OpeningHoursExpression {
18 pub rules: Vec<RuleSequence>,
19}
20
21impl OpeningHoursExpression {
22 pub fn is_constant(&self) -> bool {
36 let Some(state) = self.rules.last().map(|rs| rs.as_state()) else {
37 return true;
38 };
39
40 let search_tail_full = self.rules.iter().rev().find(|rs| {
42 rs.day_selector.is_empty() || !rs.time_selector.is_00_24() || rs.as_state() != state
43 });
44
45 let Some(tail) = search_tail_full else {
46 return state == Default::default();
47 };
48
49 tail.as_state() == state && tail.is_constant()
50 }
51
52 pub fn normalize(self) -> Self {
60 let mut rules_queue = self.rules.into_iter().peekable();
61 let mut paving = Paving5D::default();
62
63 #[allow(clippy::result_large_err)]
64 while let Some((rule, selector)) = rules_queue.next_if_map(|rule| {
65 if rule.operator == RuleOperator::Fallback {
67 return Err(rule);
68 }
69
70 let Some(selector) = ruleseq_to_selector(&rule) else {
72 return Err(rule);
73 };
74
75 Ok((rule, selector))
76 }) {
77 if rule.operator == RuleOperator::Normal && rule.kind != RuleKind::Closed {
80 let mut full_day_selector = selector.clone();
81 full_day_selector.substitute_back([Bounded::bounds()]);
82 paving.set(&full_day_selector, &Default::default());
83 }
84
85 paving.set(&selector, &(rule.kind, rule.comment));
86 }
87
88 Self {
89 rules: canonical_to_seq(paving).chain(rules_queue).collect(),
90 }
91 }
92}
93
94impl Display for OpeningHoursExpression {
95 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96 let Some(first) = self.rules.first() else {
97 return write!(f, "closed");
98 };
99
100 write!(f, "{first}")?;
101
102 for rule in &self.rules[1..] {
103 let separator = match rule.operator {
104 RuleOperator::Normal => "; ",
105 RuleOperator::Additional => ", ",
106 RuleOperator::Fallback => " || ",
107 };
108
109 write!(f, "{separator}")?;
110
111 rule.display(f, rule.operator == RuleOperator::Additional)?;
116 }
117
118 Ok(())
119 }
120}
121
122#[derive(Clone, Debug, Hash, PartialEq, Eq)]
125pub struct RuleSequence {
126 pub day_selector: day::DaySelector,
127 pub time_selector: time::TimeSelector,
128 pub kind: RuleKind,
129 pub operator: RuleOperator,
130 pub comment: Arc<str>,
131}
132
133impl RuleSequence {
134 pub fn is_constant(&self) -> bool {
137 self.day_selector.is_empty() && self.time_selector.is_00_24()
138 }
139
140 pub fn as_state(&self) -> (RuleKind, &str) {
143 (self.kind, &self.comment)
144 }
145
146 pub(crate) fn display(
151 &self,
152 f: &mut core::fmt::Formatter<'_>,
153 force_day_selector: bool,
154 ) -> core::fmt::Result {
155 let mut is_empty;
156
157 if self.is_constant() {
158 is_empty = false;
159 write!(f, "24/7")?;
160 } else {
161 self.day_selector.display(f, force_day_selector)?;
162 is_empty = !force_day_selector && self.day_selector.is_empty();
163
164 if !self.time_selector.is_00_24() {
165 if !is_empty {
166 write!(f, " ")?;
167 }
168
169 is_empty = is_empty && self.time_selector.is_00_24();
170 write!(f, "{}", self.time_selector)?;
171 }
172 }
173
174 if self.kind != RuleKind::Open {
175 if !is_empty {
176 write!(f, " ")?;
177 }
178
179 is_empty = false;
180 write!(f, "{}", self.kind)?;
181 }
182
183 if !self.comment.is_empty() {
184 if !is_empty {
185 write!(f, " ")?;
186 }
187
188 write!(f, "\"{}\"", self.comment)?;
189 }
190
191 Ok(())
192 }
193}
194
195impl Display for RuleSequence {
196 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
197 self.display(f, false)
198 }
199}
200
201#[derive(Copy, Clone, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)]
204pub enum RuleKind {
205 Open,
206 #[default]
207 Closed,
208 Unknown,
209}
210
211impl RuleKind {
212 pub const fn as_str(self) -> &'static str {
213 match self {
214 Self::Open => "open",
215 Self::Closed => "closed",
216 Self::Unknown => "unknown",
217 }
218 }
219}
220
221impl FromStr for RuleKind {
222 type Err = String;
223
224 fn from_str(s: &str) -> Result<Self, Self::Err> {
225 match s.to_lowercase().as_str() {
226 "open" => Ok(Self::Open),
227 "closed" => Ok(Self::Closed),
228 "unknown" => Ok(Self::Unknown),
229 other => Err(format!("Unknown rule kind {other:?}")),
230 }
231 }
232}
233
234impl Display for RuleKind {
235 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
236 write!(f, "{}", self.as_str())
237 }
238}
239
240#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
243pub enum RuleOperator {
244 Normal,
245 Additional,
246 Fallback,
247}