1use std::fmt::{self, Debug, Display};
25
26use once_cell::sync::Lazy;
27use regex::Regex;
28
29const STOP_CMD: &str = "stop";
30
31static TIMESTAMP_RE: Lazy<Regex> = Lazy::new(|| {
36 Regex::new(r"\A(\d{4}[-/](?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12][0-9]|3[01]) (?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-6][0-9])").expect("Date time Regex failed.")
37});
38static LAX_LINE_RE: Lazy<Regex> = Lazy::new(|| {
40 Regex::new(r"\A(\d{4}[-/](?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12][0-9]|3[01]) (?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-6][0-9])(.)(.+)").expect("Entry line Regex failed.")
41});
42pub static PROJECT_RE: Lazy<Regex> =
44 Lazy::new(|| Regex::new(r"\+(\S+)").expect("Entry project regex failed."));
45static TASKNAME_RE: Lazy<Regex> =
47 Lazy::new(|| Regex::new(r"@(\S+)").expect("Task name Regex failed."));
48static STOP_LINE: Lazy<Regex> = Lazy::new(|| {
50 Regex::new(r"\A(\d{4}[-/](?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12][0-9]|3[01]) (?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-6][0-9]) stop").expect("Stop line Regex failed")
51});
52pub static YEAR_RE: Lazy<Regex> =
54 Lazy::new(|| Regex::new(r"^(\d\d\d\d)").expect("Date regex failed"));
55pub static MARKER_RE: Lazy<Regex> =
57 Lazy::new(|| Regex::new(r"^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(.)").expect("Marker regex failed"));
58
59#[doc(inline)]
60use crate::date::{Date, DateTime};
61
62pub mod error;
63pub mod kind;
64
65pub use error::EntryError;
67pub use kind::EntryKind;
69
70#[derive(Debug, Clone, Eq, PartialEq)]
75#[must_use]
76pub struct Entry {
77 time: DateTime,
79 project: Option<String>,
81 text: String,
83 kind: EntryKind
85}
86
87impl Entry {
89 pub fn task_breakdown(entry_text: &str) -> (Option<String>, Option<String>) {
91 if entry_text.is_empty() {
92 return (None, None);
93 }
94
95 let task = PROJECT_RE.replace(entry_text, "").trim().to_string();
96 if let Some(caps) = TASKNAME_RE.captures(&task) {
97 if let Some(tname) = caps.get(1) {
98 let detail = TASKNAME_RE.replace(&task, "").trim().to_string();
99 let tname = tname.as_str().to_string();
100 return (Some(tname), (!detail.is_empty()).then_some(detail));
101 }
102 }
103 (None, (!task.is_empty()).then_some(task))
104 }
105
106 pub fn is_stop_line(line: &str) -> bool { STOP_LINE.is_match(line) }
108
109 pub fn datetime_from_line(line: &str) -> Option<&str> {
111 if line.is_empty() || Self::is_comment_line(line) {
112 return None;
113 }
114
115 if let Some(caps) = LAX_LINE_RE.captures(line) {
116 return caps.get(1).map(|s| s.as_str());
117 }
118 None
119 }
120
121 pub fn date_from_line(line: &str) -> Option<&str> {
123 Self::datetime_from_line(line).and_then(|s| s.split_whitespace().next())
124 }
125
126 pub fn extract_year(line: &str) -> Option<i32> {
128 if Self::is_comment_line(line) {
129 return None;
130 }
131
132 YEAR_RE
133 .captures(line)
134 .and_then(|cap| cap[0].parse::<i32>().ok())
135 }
136
137 pub fn is_comment_line(line: &str) -> bool { line.starts_with('#') }
139}
140
141impl Entry {
143 pub fn new(entry_text: &str, time: DateTime) -> Self {
145 Self::new_marked(entry_text, time, EntryKind::Start)
146 }
147
148 pub fn new_marked(entry_text: &str, time: DateTime, kind: EntryKind) -> Self {
151 let kind = if kind == EntryKind::Start && entry_text == STOP_CMD {
152 EntryKind::Stop
153 }
154 else {
155 kind
156 };
157 let oproject = PROJECT_RE.captures(entry_text)
158 .and_then(|caps| caps.get(1).map(|m| String::from(m.as_str())));
159 Self { time, project: oproject, text: entry_text.into(), kind }
160 }
161
162 pub fn new_stop(time: DateTime) -> Self { Self::new_marked(STOP_CMD, time, EntryKind::Stop) }
164
165 pub fn from_line(line: &str) -> Result<Self, EntryError> {
173 if line.is_empty() {
174 return Err(EntryError::BlankLine);
175 }
176
177 match LAX_LINE_RE.captures(line) {
178 Some(caps) => {
179 let Some(stamp) = caps.get(1) else { return Err(EntryError::InvalidTimeStamp); };
180 let Ok(time) = DateTime::try_from(stamp.as_str()) else {
181 return Err(EntryError::InvalidTimeStamp);
182 };
183 let kind = EntryKind::try_new(caps.get(2).and_then(|m| m.as_str().chars().next()))?;
184 Ok(Entry::new_marked(
185 caps.get(3).map_or("", |m| m.as_str()),
186 time,
187 kind
188 ))
189 },
190 None => Err(if TIMESTAMP_RE.is_match(line) {
191 EntryError::MissingTask
192 }
193 else {
194 EntryError::InvalidTimeStamp
195 })
196 }
197 }
198}
199
200impl Entry {
202 pub fn project(&self) -> Option<&str> { self.project.as_deref() }
204
205 pub fn entry_text(&self) -> &str { &self.text }
207
208 pub fn task(&self) -> Option<String> { Self::task_breakdown(&self.text).0 }
210
211 pub fn detail(&self) -> Option<String> { Self::task_breakdown(&self.text).1 }
213
214 pub fn task_and_detail(&self) -> (Option<String>, Option<String>) {
216 Self::task_breakdown(&self.text)
217 }
218
219 pub fn epoch(&self) -> i64 { self.time.timestamp() }
221
222 pub fn date(&self) -> Date { self.time.date() }
224
225 pub fn date_time(&self) -> DateTime { self.time }
227
228 #[rustfmt::skip]
230 pub fn timestamp(&self) -> String {
231 format!("{} {:02}:{:02}", self.time.date(), self.time.hour(), self.time.minute())
232 }
233
234 pub fn stamp(&self) -> String { self.date().to_string() }
236
237 pub fn is_start(&self) -> bool { self.kind == EntryKind::Start }
239
240 pub fn is_stop(&self) -> bool { self.kind == EntryKind::Stop }
242
243 pub fn is_ignore(&self) -> bool { self.kind == EntryKind::Ignored }
245
246 pub fn ignore(&self) -> Self { Self { kind: EntryKind::Ignored, ..self.clone() } }
248
249 pub fn is_event(&self) -> bool { self.kind == EntryKind::Event }
251}
252
253impl Entry {
255 pub fn change_date_time(&self, date_time: DateTime) -> Self {
258 let mut entry = self.clone();
259 entry.time = date_time;
260 entry
261 }
262
263 pub fn change_text(&self, task: &str) -> Self {
266 if self.is_stop() { return self.clone(); }
267 Self::new_marked(task, self.time, self.kind)
268 }
269
270 pub fn to_day_end(&self) -> Self { Self { time: self.date().day_end(), ..self.clone() } }
272}
273
274impl Display for Entry {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 let mark = match self.kind {
278 EntryKind::Ignored => '!',
279 EntryKind::Event => '^',
280 _ => ' '
281 };
282 write!(f, "{}{mark}{}", self.time, self.text)
283 }
284}
285
286impl PartialOrd for Entry {
287 #[rustfmt::skip]
289 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
290 Some(self.time.cmp(&other.time)
291 .then_with(|| self.text.cmp(&other.text)))
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use spectral::prelude::*;
298
299 use super::*;
300
301 const CANONICAL_LINE: &str = "2013-06-05 10:00:02 +proj1 @do something";
302 const IGNORED_LINE: &str = "2013-06-05 10:00:02!+proj1 @do something";
303 const EVENT_LINE: &str = "2013-06-05 10:00:02^+proj1 @do something";
304 const STOP_LINE: &str = "2013-06-05 10:00:02 stop";
305
306 fn reference_time() -> i64 { DateTime::new((2013, 6, 5), (10, 0, 2)).unwrap().timestamp() }
307
308 #[test]
309 fn from_line_error_if_empty() {
310 assert_that!(Entry::from_line("")).is_err_containing(EntryError::BlankLine);
311 }
312
313 #[test]
314 #[rustfmt::skip]
315 fn is_comment_on_comment() {
316 assert_that!(Entry::is_comment_line("# Random comment")).is_true();
317 assert_that!(Entry::is_comment_line("#2013-06-05 10:00:02 +test @Commented")).is_true();
318 }
319
320 #[test]
321 #[rustfmt::skip]
322 fn is_comment_on_empty() {
323 assert_that!(Entry::is_comment_line("")).is_false();
324 }
325
326 #[test]
327 #[rustfmt::skip]
328 fn is_comment_on_entry() {
329 assert_that!(Entry::is_comment_line("2013-06-05 10:00:02 +test @Commented")).is_false();
330 }
331
332 #[test]
333 #[rustfmt::skip]
334 fn test_datetime_from_empty_line() {
335 assert_that!(Entry::datetime_from_line("")).is_none()
336 }
337
338 #[test]
339 #[rustfmt::skip]
340 fn test_datetime_from_commented_line() {
341 assert_that!(Entry::datetime_from_line("# Random comment")).is_none();
342 assert_that!(Entry::datetime_from_line("#2013-06-05 10:00:02 +test @Commented")).is_none()
343 }
344
345 #[test]
346 #[rustfmt::skip]
347 fn test_datetime_from_line() {
348 assert_that!(Entry::datetime_from_line(CANONICAL_LINE)).contains("2013-06-05 10:00:02");
349 }
350
351 #[test]
352 #[rustfmt::skip]
353 fn test_datetime_from_ignored_line() {
354 assert_that!(Entry::datetime_from_line(IGNORED_LINE)).contains("2013-06-05 10:00:02");
355 }
356
357 #[test]
358 #[rustfmt::skip]
359 fn test_date_from_empty_line() {
360 assert_that!(Entry::date_from_line("")).is_none()
361 }
362
363 #[test]
364 #[rustfmt::skip]
365 fn test_date_from_commented_line() {
366 assert_that!(Entry::date_from_line("# Random comment")).is_none();
367 assert_that!(Entry::date_from_line("#2013-06-05 10:00:02 +test @Commented")).is_none()
368 }
369
370 #[test]
371 fn test_date_from_line() {
372 assert_that!(Entry::date_from_line(CANONICAL_LINE)).contains("2013-06-05");
373 }
374
375 #[test]
376 fn test_date_from_ignored_line() {
377 assert_that!(Entry::date_from_line(IGNORED_LINE)).contains("2013-06-05");
378 }
379
380 #[test]
381 fn from_line_error_if_not_entry() {
382 assert_that!(Entry::from_line("This is not an entry"))
383 .is_err_containing(EntryError::InvalidTimeStamp);
384 }
385
386 #[test]
387 fn from_line_canonical_entry() {
388 let entry = Entry::from_line(CANONICAL_LINE).unwrap();
389 assert_that!(&entry.stamp()).is_equal_to(&String::from("2013-06-05"));
390 assert_that!(&entry.project()).contains_value("proj1");
391 assert_that!(&entry.entry_text()).is_equal_to("+proj1 @do something");
392 assert_that!(&entry.task()).contains_value(String::from("do"));
393 assert_that!(&entry.detail()).contains_value(String::from("something"));
394 assert_that!(&entry.task_and_detail())
395 .is_equal_to((Some(String::from("do")), Some(String::from("something"))));
396 assert_that!(&entry.epoch()).is_equal_to(&reference_time());
397 assert_that!(&entry.to_string().as_str()).is_equal_to(&CANONICAL_LINE);
398 assert_that!(&entry.is_stop()).is_false();
399 assert_that!(&entry.is_ignore()).is_false();
400 assert_that!(&entry.is_event()).is_false();
401 }
402
403 #[test]
404 fn new_canonical_entry() {
405 let canonical_time = DateTime::try_from("2013-06-05 10:00:02").unwrap();
406 let entry = Entry::new("+proj1 @do something", canonical_time);
407 assert_that!(&entry.stamp()).is_equal_to(String::from("2013-06-05"));
408 assert_that!(&entry.project()).contains_value("proj1");
409 assert_that!(&entry.entry_text()).is_equal_to("+proj1 @do something");
410 assert_that!(&entry.task()).contains_value(String::from("do"));
411 assert_that!(&entry.detail()).contains_value(String::from("something"));
412 assert_that!(&entry.task_and_detail())
413 .is_equal_to((Some(String::from("do")), Some(String::from("something"))));
414 assert_that!(&entry.epoch()).is_equal_to(&reference_time());
415 assert_that!(&entry.to_string().as_str()).is_equal_to(&CANONICAL_LINE);
416 assert_that!(&entry.is_stop()).is_false();
417 assert_that!(&entry.is_ignore()).is_false();
418 assert_that!(&entry.is_event()).is_false();
419 }
420
421 #[test]
422 fn from_line_no_task_entry() {
423 const LINE: &str = "2013-06-05 10:00:02 +proj1 do something";
424 let entry = Entry::from_line(LINE).unwrap();
425 assert_that!(&entry.stamp()).is_equal_to(&String::from("2013-06-05"));
426 assert_that!(&entry.project()).contains_value("proj1");
427 assert_that!(&entry.entry_text()).is_equal_to("+proj1 do something");
428 assert_that!(&entry.task()).is_none();
429 assert_that!(&entry.detail()).contains_value(String::from("do something"));
430 assert_that!(&entry.task_and_detail())
431 .is_equal_to((None, Some(String::from("do something"))));
432 assert_that!(&entry.epoch()).is_equal_to(&reference_time());
433 assert_that!(&entry.to_string().as_str()).is_equal_to(&LINE);
434 assert_that!(&entry.is_stop()).is_false();
435 assert_that!(&entry.is_ignore()).is_false();
436 assert_that!(&entry.is_event()).is_false();
437 }
438
439 #[test]
440 fn from_line_no_detail_entry() {
441 const LINE: &str = "2013-06-05 10:00:02 +proj1 @something";
442 let entry = Entry::from_line(LINE).unwrap();
443 assert_that!(&entry.stamp()).is_equal_to(&String::from("2013-06-05"));
444 assert_that!(&entry.project()).contains_value("proj1");
445 assert_that!(&entry.entry_text()).is_equal_to("+proj1 @something");
446 assert_that!(&entry.task()).contains_value(String::from("something"));
447 assert_that!(&entry.detail()).is_none();
448 assert_that!(&entry.task_and_detail()).is_equal_to((Some(String::from("something")), None));
449 assert_that!(&entry.epoch()).is_equal_to(&reference_time());
450 assert_that!(&entry.to_string().as_str()).is_equal_to(&LINE);
451 assert_that!(&entry.is_stop()).is_false();
452 assert_that!(&entry.is_ignore()).is_false();
453 assert_that!(&entry.is_event()).is_false();
454 }
455
456 #[test]
457 fn from_line_no_entry_text() {
458 const LINE: &str = "2013-06-05 10:00:02 +proj1";
459 let entry = Entry::from_line(LINE).unwrap();
460 assert_that!(&entry.stamp()).is_equal_to(&String::from("2013-06-05"));
461 assert_that!(&entry.project()).contains_value("proj1");
462 assert_that!(&entry.entry_text()).is_equal_to("+proj1");
463 assert_that!(&entry.task()).is_none();
464 assert_that!(&entry.detail()).is_none();
465 assert_that!(&entry.task_and_detail()).is_equal_to((None, None));
466 assert_that!(&entry.epoch()).is_equal_to(&reference_time());
467 assert_that!(&entry.to_string().as_str()).is_equal_to(&LINE);
468 assert_that!(&entry.is_stop()).is_false();
469 assert_that!(&entry.is_ignore()).is_false();
470 assert_that!(&entry.is_event()).is_false();
471 }
472
473 #[test]
474 fn from_line_stop_entry() {
475 let entry = Entry::from_line(STOP_LINE).unwrap();
476 assert_that!(&entry.stamp()).is_equal_to(String::from("2013-06-05"));
477 assert_that!(&entry.project()).is_none();
478 assert_that!(&entry.entry_text()).is_equal_to("stop");
479 assert_that!(&entry.task()).is_none();
480 assert_that!(&entry.detail()).contains_value(String::from("stop"));
481 assert_that!(&entry.task_and_detail()).is_equal_to((None, Some(String::from("stop"))));
482 assert_that!(&entry.epoch()).is_equal_to(&reference_time());
483 assert_that!(&entry.to_string().as_str()).is_equal_to(&STOP_LINE);
484 assert_that!(&entry.is_stop()).is_true();
485 assert_that!(&entry.is_ignore()).is_false();
486 assert_that!(&entry.is_event()).is_false();
487 }
488
489 #[test]
490 fn test_extract_year() {
491 let line = "2018-11-20 12:34:43 +test @Event";
492 assert_that!(Entry::extract_year(line)).contains(2018);
493 }
494
495 #[test]
496 fn test_extract_year_fail() {
497 let line = "xyzzy 2018-11-20 12:34:43 +test @Event";
498 assert_that!(Entry::extract_year(line)).is_none();
499 }
500
501 #[test]
502 fn new_stop_entry() {
503 let canonical_time = DateTime::try_from("2013-06-05 10:00:02").unwrap();
504 let entry = Entry::new("stop", canonical_time);
505 assert_that!(&entry.stamp()).is_equal_to(String::from("2013-06-05"));
506 assert_that!(&entry.project()).is_none();
507 assert_that!(&entry.entry_text()).is_equal_to("stop");
508 assert_that!(&entry.task()).is_none();
509 assert_that!(&entry.detail()).contains_value(String::from("stop"));
510 assert_that!(&entry.task_and_detail()).is_equal_to((None, Some(String::from("stop"))));
511 assert_that!(&entry.epoch()).is_equal_to(&reference_time());
512 assert_that!(&entry.to_string().as_str()).is_equal_to(&STOP_LINE);
513 assert_that!(&entry.is_stop()).is_true();
514 assert_that!(&entry.is_ignore()).is_false();
515 assert_that!(&entry.is_event()).is_false();
516 }
517
518 #[test]
519 fn from_line_ignored_entry() {
520 let entry = Entry::from_line(IGNORED_LINE).unwrap();
521 assert_that!(&entry.stamp()).is_equal_to(&String::from("2013-06-05"));
522 assert_that!(&entry.project()).contains_value("proj1");
523 assert_that!(&entry.entry_text()).is_equal_to("+proj1 @do something");
524 assert_that!(&entry.task()).contains_value(String::from("do"));
525 assert_that!(&entry.detail()).contains_value(String::from("something"));
526 assert_that!(&entry.task_and_detail())
527 .is_equal_to((Some(String::from("do")), Some(String::from("something"))));
528 assert_that!(&entry.epoch()).is_equal_to(&reference_time());
529 assert_that!(&entry.to_string().as_str()).is_equal_to(&IGNORED_LINE);
530 assert_that!(&entry.is_stop()).is_false();
531 assert_that!(&entry.is_ignore()).is_true();
532 assert_that!(&entry.is_event()).is_false();
533 }
534
535 #[test]
536 fn new_ignored_entry() {
537 let canonical_time = DateTime::try_from("2013-06-05 10:00:02").unwrap();
538 let entry = Entry::new_marked("+proj1 @do something", canonical_time, EntryKind::Ignored);
539 assert_that!(&entry.stamp()).is_equal_to(String::from("2013-06-05"));
540 assert_that!(&entry.project()).contains_value("proj1");
541 assert_that!(&entry.entry_text()).is_equal_to("+proj1 @do something");
542 assert_that!(&entry.task()).contains_value(String::from("do"));
543 assert_that!(&entry.detail()).contains_value(String::from("something"));
544 assert_that!(&entry.task_and_detail())
545 .is_equal_to((Some(String::from("do")), Some(String::from("something"))));
546 assert_that!(&entry.epoch()).is_equal_to(&reference_time());
547 assert_that!(&entry.to_string().as_str()).is_equal_to(&IGNORED_LINE);
548 assert_that!(&entry.is_stop()).is_false();
549 assert_that!(&entry.is_ignore()).is_true();
550 assert_that!(&entry.is_event()).is_false();
551 }
552
553 #[test]
554 fn from_line_event_entry() {
555 let entry = Entry::from_line(EVENT_LINE).unwrap();
556 assert_that!(&entry.stamp()).is_equal_to(&String::from("2013-06-05"));
557 assert_that!(&entry.project()).contains_value("proj1");
558 assert_that!(&entry.entry_text()).is_equal_to("+proj1 @do something");
559 assert_that!(&entry.task()).contains_value(String::from("do"));
560 assert_that!(&entry.detail()).contains_value(String::from("something"));
561 assert_that!(&entry.task_and_detail())
562 .is_equal_to((Some(String::from("do")), Some(String::from("something"))));
563 assert_that!(&entry.epoch()).is_equal_to(&reference_time());
564 assert_that!(&entry.to_string().as_str()).is_equal_to(&EVENT_LINE);
565 assert_that!(&entry.is_stop()).is_false();
566 assert_that!(&entry.is_ignore()).is_false();
567 assert_that!(&entry.is_event()).is_true();
568 }
569
570 #[test]
571 fn new_event_entry() {
572 let canonical_time = DateTime::try_from("2013-06-05 10:00:02").unwrap();
573 let entry = Entry::new_marked("+proj1 @do something", canonical_time, EntryKind::Event);
574 assert_that!(&entry.stamp()).is_equal_to(String::from("2013-06-05"));
575 assert_that!(&entry.project()).contains_value("proj1");
576 assert_that!(&entry.entry_text()).is_equal_to("+proj1 @do something");
577 assert_that!(&entry.task()).contains_value(String::from("do"));
578 assert_that!(&entry.detail()).contains_value(String::from("something"));
579 assert_that!(&entry.task_and_detail())
580 .is_equal_to((Some(String::from("do")), Some(String::from("something"))));
581 assert_that!(&entry.epoch()).is_equal_to(&reference_time());
582 assert_that!(&entry.to_string().as_str()).is_equal_to(&EVENT_LINE);
583 assert_that!(&entry.is_stop()).is_false();
584 assert_that!(&entry.is_ignore()).is_false();
585 assert_that!(&entry.is_event()).is_true();
586 }
587
588 #[test]
589 fn compare_entry() {
590 const LINE1: &str = "2013-06-05 10:00:02 +proj1";
591 const LINE2: &str = "2013-06-05 11:00:02 +proj1";
592 let entry1 = Entry::from_line(LINE1).unwrap();
593 let entry2 = Entry::from_line(LINE2).unwrap();
594 assert_that!(entry2 > entry1).is_true();
595 assert_that!(entry1 < entry2).is_true();
596 assert_that!(entry1 == entry1).is_true();
597 }
598
599 #[test]
600 fn test_change_date_time_start() {
601 let entry = Entry::from_line("2022-12-27 10:00:00 +proj1").unwrap();
602 let dt = DateTime::new((2022, 12, 27), (9, 50, 00)).unwrap();
603 let new_entry = entry.change_date_time(dt);
604
605 assert_that!(new_entry).is_not_equal_to(&entry);
606 let dt = DateTime::new((2022, 12, 27), (9, 50, 00)).unwrap();
607 assert_that!(new_entry.date_time()).is_equal_to(&dt);
608 assert_that!(new_entry.entry_text()).is_equal_to(&entry.entry_text());
609 assert_that!(new_entry.is_start()).is_true();
610 }
611
612 #[test]
613 fn test_change_date_time_event() {
614 let entry = Entry::from_line("2022-12-27 10:00:00^+event").unwrap();
615 let dt = DateTime::new((2022, 12, 27), (9, 50, 00)).unwrap();
616 let new_entry = entry.change_date_time(dt);
617
618 assert_that!(new_entry).is_not_equal_to(&entry);
619 let dt = DateTime::new((2022, 12, 27), (9, 50, 00)).unwrap();
620 assert_that!(new_entry.date_time()).is_equal_to(&dt);
621 assert_that!(new_entry.entry_text()).is_equal_to(&entry.entry_text());
622 assert_that!(new_entry.is_event()).is_true();
623 }
624
625 #[test]
626 fn test_change_date_time_ignored() {
627 let entry = Entry::from_line("2022-12-27 10:00:00!+proj1").unwrap();
628 let dt = DateTime::new((2022, 12, 27), (9, 50, 00)).unwrap();
629 let new_entry = entry.change_date_time(dt);
630
631 assert_that!(new_entry).is_not_equal_to(&entry);
632 let dt = DateTime::new((2022, 12, 27), (9, 50, 00)).unwrap();
633 assert_that!(new_entry.date_time()).is_equal_to(&dt);
634 assert_that!(new_entry.entry_text()).is_equal_to(&entry.entry_text());
635 assert_that!(new_entry.is_ignore()).is_true();
636 }
637
638 #[test]
639 fn test_change_date_time_stop() {
640 let entry = Entry::from_line("2022-12-27 10:00:00 stop").unwrap();
641 let dt = DateTime::new((2022, 12, 27), (9, 50, 00)).unwrap();
642 let new_entry = entry.change_date_time(dt);
643
644 assert_that!(new_entry).is_not_equal_to(&entry);
645 let dt = DateTime::new((2022, 12, 27), (9, 50, 00)).unwrap();
646 assert_that!(new_entry.date_time()).is_equal_to(&dt);
647 assert_that!(new_entry.entry_text()).is_equal_to(&entry.entry_text());
648 assert_that!(new_entry.is_stop()).is_true();
649 }
650
651 #[test]
652 fn test_change_text_start() {
653 let entry = Entry::from_line("2022-12-27 10:00:00 +proj1").unwrap();
654 let new_entry = entry.change_text("+proj2 @Changed");
655
656 assert_that!(new_entry).is_not_equal_to(&entry);
657 assert_that!(new_entry.date_time()).is_equal_to(&entry.date_time());
658 assert_that!(new_entry.entry_text()).is_equal_to("+proj2 @Changed");
659 assert_that!(new_entry.is_start()).is_true();
660 }
661
662 #[test]
663 fn test_change_text_event() {
664 let entry = Entry::from_line("2022-12-27 10:00:00^+event").unwrap();
665 let new_entry = entry.change_text("+proj2 @Changed");
666
667 assert_that!(new_entry).is_not_equal_to(&entry);
668 assert_that!(new_entry.date_time()).is_equal_to(&entry.date_time());
669 assert_that!(new_entry.entry_text()).is_equal_to("+proj2 @Changed");
670 assert_that!(new_entry.is_event()).is_true();
671 }
672
673 #[test]
674 fn test_change_text_ignored() {
675 let entry = Entry::from_line("2022-12-27 10:00:00!+proj1").unwrap();
676 let new_entry = entry.change_text("+proj2 @Changed");
677
678 assert_that!(new_entry).is_not_equal_to(&entry);
679 assert_that!(new_entry.date_time()).is_equal_to(&entry.date_time());
680 assert_that!(new_entry.entry_text()).is_equal_to("+proj2 @Changed");
681 assert_that!(new_entry.is_ignore()).is_true();
682 }
683
684 #[test]
685 fn test_change_text_stop() {
686 let entry = Entry::from_line("2022-12-27 10:00:00 stop").unwrap();
687 let new_entry = entry.change_text("+proj2 @Changed");
688
689 assert_that!(new_entry).is_equal_to(&entry);
690 assert_that!(new_entry.date_time()).is_equal_to(&entry.date_time());
691 assert_that!(new_entry.entry_text()).is_equal_to("stop");
692 assert_that!(new_entry.is_stop()).is_true();
693 }
694}