1use super::prelude::*;
4use regex::Regex;
5
6#[derive(Debug, PartialEq, Clone)]
8pub enum Range {
9 None,
11 All,
13 Count(usize),
15 At(Vec<usize>),
17 PositionRange(usize, usize),
19 FromPosition(usize),
21 Day(Date),
23 TimeRange(DateTime, DateTime),
25 Since(DateTime),
27}
28
29impl Range {
30 pub fn parse(list: Option<String>, context: &Context) -> Result<Self, Error> {
32 if let Some(list) = list {
33 match Self::parse_count(&list).or(Self::parse_at(&list).or(Self::parse_position_range(
34 &list,
35 )
36 .or(
37 Self::parse_time_range(&list, context).or(Self::parse_day(&list, context)
38 .or(Self::parse_from_position(&list).or(Self::parse_since(&list, context)))),
39 ))) {
40 Range::None => Err(Error::RangeFormat(list)),
41 range => Ok(range),
42 }
43 } else {
44 Ok(Range::All)
45 }
46 }
47 fn or(self, other: Self) -> Self {
49 match self {
50 Self::None => other,
51 _ => self,
52 }
53 }
54 fn parse_count(list: &str) -> Range {
56 let re = Regex::new(r"^~(\d+)$").unwrap();
57
58 if let Some(cap) = re.captures_iter(list).next() {
59 return Self::Count(cap[1].parse::<usize>().unwrap());
60 }
61 Self::None
62 }
63 fn parse_at(list: &str) -> Range {
65 let re = Regex::new(r"^([\d,]+)$").unwrap();
66 if let Some(cap) = re.captures_iter(list).next() {
67 return Self::At(
68 cap[1]
69 .split(',')
70 .map(|c| c.parse::<usize>().unwrap() - 1)
71 .collect(),
72 );
73 }
74 Self::None
75 }
76 fn parse_position_range(list: &str) -> Range {
78 let re = Regex::new(r"^(\d+)-(\d+)$").unwrap();
79 if let Some(cap) = re.captures_iter(list).next() {
80 return Self::PositionRange(
81 cap[1].parse::<usize>().unwrap() - 1,
82 cap[2].parse::<usize>().unwrap() - 1,
83 );
84 }
85 Self::None
86 }
87 fn parse_from_position(list: &str) -> Range {
89 let re = Regex::new(r"^(\d+)-$").unwrap();
90 if let Some(cap) = re.captures_iter(list).next() {
91 return Self::FromPosition(cap[1].parse::<usize>().unwrap() - 1);
92 }
93 Self::None
94 }
95 fn parse_day(list: &str, context: &Context) -> Range {
97 let pt = PartialDateTime::parse_opt(Some(list.to_string()));
98 match pt {
99 PartialDateTime::None => Self::None,
100 _ => Range::Day(Date::from(pt.into(context.time()))),
101 }
102 }
103 fn parse_time_range(list: &str, context: &Context) -> Range {
105 let list: Vec<String> = if list.contains("...") {
106 let list: Vec<&str> = list.split("...").collect();
107 vec![list[0].to_string() + ".", list[1].to_string()]
108 } else {
109 let list: Vec<&str> = list.split("..").collect();
110 match list.len() {
111 1 => vec![list[0].to_string()],
112 2 => vec![list[0].to_string(), list[1].to_string()],
113 _ => return Range::None,
114 }
115 };
116 if list.len() == 2 {
117 let from = PartialDateTime::parse_opt(Some(list[0].to_string()));
118 let to = PartialDateTime::parse_opt(Some(list[1].to_string()));
119 match (from, to) {
120 (PartialDateTime::None, PartialDateTime::None) => Self::None,
121 (from, PartialDateTime::None) => {
122 Self::TimeRange(from.into(context.time()), context.time())
123 }
124 (PartialDateTime::None, to) => {
125 use chrono::{TimeZone, Utc};
126 Self::TimeRange(
127 Utc.with_ymd_and_hms(1900, 1, 1, 0, 0, 0).unwrap().into(),
128 to.into(context.time()) + Duration::days(1),
129 )
130 }
131 (from, to) => {
132 let from = from.into(context.time());
133 Self::TimeRange(from, to.into(from) + Duration::days(1))
134 }
135 }
136 } else {
137 Self::None
138 }
139 }
140 fn parse_since(list: &str, context: &Context) -> Range {
142 let re = Regex::new(r"^(.+)\.\.$").unwrap();
143 if let Some(cap) = re.captures_iter(list).next() {
144 let pt = PartialDateTime::parse_opt(Some(cap[1].to_string()));
145 return match pt {
146 PartialDateTime::None => Self::None,
147 _ => Range::Since(pt.into(context.time())),
148 };
149 }
150 Self::None
151 }
152}
153
154impl std::fmt::Display for Range {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 match self {
157 Self::None => write!(f, "none"),
158 Self::All => write!(f, "all job(s)"),
159 Self::Count(count) => write!(f, "last {count} job(s)"),
160 Self::At(pos) => write!(
161 f,
162 "job at position(s) {pos}",
163 pos = {
164 let v: Vec<String> = pos.iter().map(|p| p.to_string()).collect();
165 v.join(",")
166 }
167 ),
168 Self::PositionRange(from, to) => write!(
169 f,
170 "job(s) from position {from} to {to}",
171 from = from + 1,
172 to = to + 1
173 ),
174 Self::FromPosition(from) => write!(f, "job(s) from position {from}", from = from + 1),
175 Self::Day(day) => write!(f, "job(s) at {day}"),
176 Self::TimeRange(since, until) => write!(f, "job(s) since {since} until {until}"),
177 Self::Since(since) => write!(f, "job(s) since {since}"),
178 }
179 }
180}
181
182#[cfg(test)]
183fn new_job(start: &str, end: Option<&str>, message: Option<&str>, tags: Option<&str>) -> Job {
184 Job::new(
185 start.into(),
186 end.map(|end| end.into()),
187 message.map(|message| message.to_string()),
188 tags.map(|tags| tags.into()),
189 )
190 .expect("can't parse new job")
191}
192
193#[test]
195fn test_parse_time_range() {
196 let context = Context::new_test("2023-2-1 12:00");
198
199 let mut jobs = Jobs::new();
201 jobs._push(new_job(
202 "2022-12-31 18:00",
203 Some("2022-12-31 23:59"),
204 None,
205 None,
206 ));
207 jobs._push(new_job(
208 "2023-01-01 00:00",
209 Some("2023-01-01 00:10"),
210 None,
211 None,
212 ));
213
214 let january = Range::parse(Some("1.1...31.1.".into()), &context).unwrap();
216
217 assert!(jobs._filter(&january, &TagSet::new()).unwrap().len() == 1);
218
219 assert_eq!(
220 january,
221 Range::TimeRange("2023-1-1 00:00".into(), "2023-2-1 00:00".into())
222 );
223}
224
225#[test]
227fn test_parse_fails() {
228 let context = Context::new_test("2023-2-1 12:00");
230
231 assert!(Range::parse(Some("1.1.-".into()), &context).is_err());
232}