1extern crate chrono;
3extern crate clap;
4extern crate larry;
5extern crate pidgin;
6extern crate regex;
7extern crate serde_json;
8use crate::configure::Configuration;
9use crate::util::{duration_string, log_path};
10use chrono::{Datelike, Duration, Local, NaiveDate, NaiveDateTime, Timelike};
11use clap::ArgMatches;
12use larry::Larry;
13use pidgin::{Grammar, Matcher};
14use regex::{Regex, RegexSet};
15use std::fs::{File, OpenOptions};
16use std::io::{BufRead, BufReader, Lines, Write};
17use std::path::PathBuf;
18
19lazy_static! {
20 #[doc(hidden)]
23 pub static ref LOG_LINES: Grammar = grammar!{
24
25 TOP -> r(r"\A") <log_item> r(r"\z")
26
27 log_item -> <timestamped_item> | <blank> | <comment>
28 blank -> r(r"\s*")
29 comment -> r(r"\s*#.*")
30 timestamped_item -> <timestamp> <ti_continuation>
31 timestamp -> r(r"\s*[1-9]\d{3}(?:\s+\d{1,2}){5}\s*")
32 ti_continuation -> <taggable> | <done>
33 taggable -> <tag_separator> <tags> (":") <description>
34 tag_separator -> <event> | <note>
35 event -> (":")
36 note -> ("<NOTE>")
37 done -> r(r":DONE\s*")
38 tags -> r(r"(?:\\.|[^:<\\])*") description -> r(r".*")
40 };
41 pub static ref MATCHER: Matcher = LOG_LINES.matcher().unwrap();
42}
43
44pub fn parse_line(line: &str, offset: usize) -> Item {
46 if let Some(ast) = MATCHER.parse(line) {
47 if let Some(timestamp) = ast.name("timestamp") {
48 match parse_timestamp(timestamp.as_str()) {
49 Err(s) => Item::Error(s, offset),
50 Ok(timestamp) => {
51 if ast.has("done") {
52 Item::Done(Done(timestamp), offset)
53 } else {
54 let tags = parse_tags(ast.name("tags").unwrap().as_str());
55 let description = ast.name("description").unwrap().as_str();
56 if ast.has("event") {
57 Item::Event(
58 Event {
59 start: timestamp,
60 start_overlap: false,
61 end: None,
62 end_overlap: false,
63 description: description.to_owned(),
64 tags: tags,
65 vacation: false,
66 vacation_type: None,
67 },
68 offset,
69 )
70 } else {
71 Item::Note(
72 Note {
73 time: timestamp,
74 description: description.to_owned(),
75 tags: tags,
76 },
77 offset,
78 )
79 }
80 }
81 }
82 }
83 } else if ast.has("blank") {
84 Item::Blank(offset)
85 } else {
86 Item::Comment(offset)
87 }
88 } else {
89 Item::Error(String::from("unexpected line format"), offset)
90 }
91}
92
93pub struct LogController {
94 pub larry: Larry,
95 pub path: String,
96}
97
98impl LogController {
99 pub fn new(
100 log: Option<PathBuf>,
101 conf: &Configuration,
102 ) -> Result<LogController, std::io::Error> {
103 let log = log.unwrap_or(log_path(conf.directory()));
104 let path = log.as_path().to_str();
105 Larry::new(log.as_path()).and_then(|log| {
106 Ok(LogController {
107 larry: log,
108 path: path.unwrap().to_owned(),
109 })
110 })
111 }
112 pub fn find_line(&mut self, time: &NaiveDateTime) -> Option<Item> {
116 if let Some(start) = self.get_after(0) {
117 let end = self.get_before(self.larry.len() - 1);
118 let time = start.advance(time);
119 Some(self.narrow_in(&time, start, end))
120 } else {
121 None
122 }
123 }
124 pub fn first_timestamp(&self) -> Option<NaiveDateTime> {
125 let item = ItemsAfter::new(0, &self.path).find(|i| i.has_time());
126 item.and_then(|i| Some(i.time().unwrap().0.clone()))
127 }
128 pub fn last_timestamp(&mut self) -> Option<NaiveDateTime> {
129 let item = ItemsBefore::new(self.larry.len(), self).find(|i| i.has_time());
130 item.and_then(|i| Some(i.time().unwrap().0.clone()))
131 }
132 fn narrow_in(&mut self, time: &NaiveDateTime, start: Item, end: Item) -> Item {
133 let start = self.advance_to_first(start);
134 let (t1, mut o1) = start.time().unwrap();
135 if t1 == time {
136 return start;
137 }
138 let (t2, o2) = end.time().unwrap();
139 if t2 == time {
140 return end;
141 } else if t1 == t2 {
142 return start;
143 }
144 let mut o3 = self.estimate(time, t1, o1, t2, o2);
148 if o3 == o1 {
149 return start;
150 }
151 loop {
152 let next = self.get_before(o3);
153 if next == start {
154 o1 = o3;
156 o3 = self.estimate(time, t1, o1, t2, o2);
157 if o3 == o1 {
158 return start;
159 }
160 } else {
161 if let Some((t, _)) = next.time() {
162 if t == time {
163 return next;
164 } else if t < time {
165 return self.narrow_in(time, next, end);
166 } else {
167 return self.narrow_in(time, start, next);
168 }
169 } else {
170 unreachable!()
171 }
172 }
173 }
174 }
175 fn estimate(
178 &self,
179 time: &NaiveDateTime,
180 t1: &NaiveDateTime,
181 o1: usize,
182 t2: &NaiveDateTime,
183 o2: usize,
184 ) -> usize {
185 let line_delta = o2 - o1;
186 match line_delta {
187 1 => o1,
188 2 => o1 + 1,
189 _ => {
190 if line_delta <= 16 {
191 return o1 + line_delta / 2;
194 }
195 let time_delta = t2.timestamp() - t1.timestamp();
196 let lines_per_second = (line_delta as f64) / (time_delta as f64);
197 let seconds = (time.timestamp() - t1.timestamp()) as f64;
198 let additional_lines = (lines_per_second * seconds) as usize;
199 let additional_lines = if additional_lines == 0 {
201 1
202 } else if additional_lines == line_delta {
203 line_delta - 1
204 } else {
205 additional_lines
206 };
207 o1 + additional_lines
208 }
209 }
210 }
211 fn get_after(&mut self, i: usize) -> Option<Item> {
214 for i in i..self.larry.len() {
215 let item = parse_line(self.larry.get(i).unwrap(), i);
216 let t = item.time();
217 if let Some((_, _)) = t {
218 return Some(item);
219 }
220 }
221 None
222 }
223 pub fn items_before(&mut self, offset: usize) -> ItemsBefore {
225 ItemsBefore::new(offset, self)
226 }
227 fn get_before(&mut self, i: usize) -> Item {
230 let mut i = i;
231 if i >= self.larry.len() {
232 i = self.larry.len() - 1;
233 }
234 loop {
235 let item = parse_line(self.larry.get(i).unwrap(), i);
236 match item {
237 Item::Done(_, _) | Item::Note(_, _) | Item::Event(_, _) => return item,
238 _ => (),
239 }
240 if i == 0 {
241 break;
242 }
243 i -= 1;
244 }
245 unreachable!()
246 }
247 fn advance_to_first(&mut self, item: Item) -> Item {
250 let (time, mut i) = item.time().unwrap();
251 let mut ptr = item.clone();
252 while i > 0 {
253 i -= 1;
254 let next = parse_line(self.larry.get(i).unwrap(), i);
255 let next_time = next.time();
256 if let Some((next_time, _)) = next_time {
257 if time == next_time {
258 ptr = next;
259 } else if time > next_time {
260 return ptr;
261 }
262 }
263 }
264 ptr
265 }
266 pub fn events_from_the_end(&mut self) -> EventsBefore {
267 EventsBefore::new(self.larry.len(), self)
268 }
269 pub fn notes_from_the_end(&mut self) -> NotesBefore {
270 NotesBefore::new(self.larry.len(), self)
271 }
272 pub fn events_from_the_beginning(self) -> EventsAfter {
273 EventsAfter::new(0, &self)
274 }
275 pub fn notes_from_the_beginning(self) -> NotesAfter {
276 NotesAfter::new(0, &self)
277 }
278 pub fn events_in_range(&mut self, start: &NaiveDateTime, end: &NaiveDateTime) -> Vec<Event> {
279 let mut ret = vec![];
280 if let Some(item) = self.find_line(start) {
281 for e in EventsAfter::new(item.offset(), self) {
282 if &e.start < end {
283 ret.push(e);
284 } else {
285 break;
286 }
287 }
288 }
289 ret
290 }
291 pub fn tagable_items_in_range(
292 &mut self,
293 start: &NaiveDateTime,
294 end: &NaiveDateTime,
295 ) -> Vec<Item> {
296 let mut ret = vec![];
297 if let Some(item) = self.find_line(start) {
298 for i in ItemsAfter::new(item.offset(), &self.path) {
299 match &i {
300 Item::Note(n, _) => {
301 if &n.time > end {
302 break;
303 } else {
304 ret.push(i);
305 }
306 }
307 Item::Event(e, _) => {
308 if &e.start > end {
309 break;
310 } else {
311 ret.push(i);
312 }
313 }
314 _ => (),
315 }
316 }
317 }
318 ret
319 }
320 pub fn notes_in_range(&mut self, start: &NaiveDateTime, end: &NaiveDateTime) -> Vec<Note> {
321 let mut ret = vec![];
322 if let Some(item) = self.find_line(start) {
323 let mut at_first = true;
324 for n in NotesAfter::new(item.offset(), self) {
325 if at_first && &n.time < start {
326 at_first = false;
327 continue;
328 } else {
329 at_first = false
330 }
331 if &n.time < end {
332 ret.push(n);
333 } else {
334 break;
335 }
336 }
337 }
338 ret
339 }
340 pub fn last_event(&mut self) -> Option<Event> {
341 self.events_from_the_end().find(|_| true)
343 }
344 pub fn forgot_to_end_last_event(&mut self) -> bool {
345 if let Some(event) = self.last_event() {
346 if event.ongoing() {
347 let now = Local::now().naive_local();
348 event.start.date() != now.date()
349 } else {
350 false
351 }
352 } else {
353 false
354 }
355 }
356 fn needs_newline(&mut self) -> bool {
357 if self.larry.len() > 0 {
358 let last_line = self
359 .larry
360 .get(self.larry.len() - 1)
361 .expect("could not obtain last line of log");
362 let last_char = last_line.bytes().last().unwrap();
363 !(last_char == 0x0D || last_char == 0x0A)
364 } else {
365 false
366 }
367 }
368 pub fn append_event(&mut self, description: String, tags: Vec<String>) -> (Event, usize) {
370 let event = Event::coin(description, tags);
371 self.append_to_log(event, "could not append event to log")
372 }
373 pub fn append_note(&mut self, description: String, tags: Vec<String>) -> (Note, usize) {
375 let note = Note::coin(description, tags);
376 self.append_to_log(note, "could not append note to log")
377 }
378 pub fn close_event(&mut self) -> (Done, usize) {
379 let done = Done(Local::now().naive_local());
380 self.append_to_log(done, "could not append DONE line to log")
381 }
382 pub fn append_to_log<T: LogLine>(&mut self, item: T, error_message: &str) -> (T, usize) {
383 let mut log = OpenOptions::new()
384 .write(true)
385 .append(true)
386 .open(&self.path)
387 .unwrap();
388 if self.needs_newline() {
389 writeln!(log, "").expect("could not append to log file");
390 }
391 let now = Local::today().naive_local();
392 if let Some(ts) = self.last_timestamp() {
393 if ts.date() != now {
394 writeln!(log, "# {}/{}/{}", now.year(), now.month(), now.day())
395 .expect("could not append date comment to log");
396 }
397 } else {
398 writeln!(log, "# {}/{}/{}", now.year(), now.month(), now.day())
399 .expect("could not append date comment to log");
400 }
401 writeln!(log, "{}", &item.to_line()).expect(error_message);
402 (item, self.larry.len())
403 }
404 pub fn items(&self) -> ItemsAfter {
406 ItemsAfter::new(0, &self.path)
407 }
408}
409
410pub struct ItemsBefore<'a> {
411 offset: Option<usize>,
412 larry: &'a mut Larry,
413}
414
415impl<'a> ItemsBefore<'a> {
416 fn new(offset: usize, reader: &mut LogController) -> ItemsBefore {
417 ItemsBefore {
418 offset: if offset == 0 { None } else { Some(offset) },
419 larry: &mut reader.larry,
420 }
421 }
422}
423
424impl<'a> Iterator for ItemsBefore<'a> {
425 type Item = Item;
426 fn next(&mut self) -> Option<Item> {
427 if let Some(o) = self.offset {
428 let o2 = o - 1;
429 let line = self.larry.get(o2).unwrap();
430 let item = parse_line(line, o);
431 self.offset = if o2 > 0 { Some(o2) } else { None };
432 Some(item)
433 } else {
434 None
435 }
436 }
437}
438
439pub struct ItemsAfter {
440 offset: usize,
441 bufreader: Lines<BufReader<File>>,
442}
443
444impl ItemsAfter {
445 pub fn new(offset: usize, path: &str) -> ItemsAfter {
446 let mut bufreader =
447 BufReader::new(File::open(path).expect("could not open log file")).lines();
448 for _ in 0..offset {
449 bufreader.next();
450 }
451 ItemsAfter { offset, bufreader }
452 }
453}
454
455impl Iterator for ItemsAfter {
456 type Item = Item;
457 fn next(&mut self) -> Option<Item> {
458 if let Some(res) = self.bufreader.next() {
459 let line = res.expect("could not read log line");
460 let item = parse_line(&line, self.offset);
461 self.offset += 1;
462 Some(item)
463 } else {
464 None
465 }
466 }
467}
468
469pub struct NotesBefore<'a> {
470 item_iterator: ItemsBefore<'a>,
471}
472
473impl<'a> NotesBefore<'a> {
474 fn new(offset: usize, reader: &mut LogController) -> NotesBefore {
475 NotesBefore {
476 item_iterator: ItemsBefore::new(offset, reader),
477 }
478 }
479}
480
481impl<'a> Iterator for NotesBefore<'a> {
482 type Item = Note;
483 fn next(&mut self) -> Option<Note> {
484 loop {
485 let item = self.item_iterator.next();
486 if let Some(item) = item {
487 match item {
488 Item::Note(n, _) => return Some(n),
489 _ => (),
490 }
491 } else {
492 return None;
493 }
494 }
495 }
496}
497
498pub struct NotesAfter {
499 item_iterator: ItemsAfter,
500}
501
502impl NotesAfter {
503 fn new(offset: usize, reader: &LogController) -> NotesAfter {
504 NotesAfter {
505 item_iterator: ItemsAfter::new(offset, &reader.path),
506 }
507 }
508}
509
510impl Iterator for NotesAfter {
511 type Item = Note;
512 fn next(&mut self) -> Option<Note> {
513 loop {
514 let item = self.item_iterator.next();
515 if let Some(item) = item {
516 match item {
517 Item::Note(n, _) => return Some(n),
518 _ => (),
519 }
520 } else {
521 return None;
522 }
523 }
524 }
525}
526
527pub struct EventsBefore<'a> {
528 last_time: Option<NaiveDateTime>,
529 item_iterator: ItemsBefore<'a>,
530}
531
532impl<'a> EventsBefore<'a> {
533 fn new(offset: usize, reader: &mut LogController) -> EventsBefore {
534 let items_after = ItemsAfter::new(offset, &reader.path);
536 let timed_item = items_after
537 .filter(|i| match i {
538 Item::Event(_, _) | Item::Done(_, _) => true,
539 _ => false,
540 })
541 .find(|i| i.time().is_some());
542 let last_time = if let Some(i) = timed_item {
543 Some(i.time().unwrap().0.to_owned())
544 } else {
545 None
546 };
547 EventsBefore {
548 last_time,
549 item_iterator: ItemsBefore::new(offset, reader),
550 }
551 }
552}
553
554impl<'a> Iterator for EventsBefore<'a> {
555 type Item = Event;
556 fn next(&mut self) -> Option<Event> {
557 let mut last_time = self.last_time;
558 let mut event: Option<Event> = None;
559 loop {
560 if let Some(i) = self.item_iterator.next() {
561 match i {
562 Item::Event(e, _) => {
563 event = Some(e.bounded_time(last_time));
564 break;
565 }
566 Item::Done(d, _) => {
567 last_time = Some(d.0);
568 }
569 _ => (),
570 }
571 } else {
572 break;
573 }
574 }
575 self.last_time = if event.is_some() {
576 Some(event.as_ref().unwrap().start.clone())
577 } else {
578 last_time
579 };
580 event
581 }
582}
583
584pub struct EventsAfter {
585 next_item: Option<Event>,
586 item_iterator: ItemsAfter,
587}
588
589impl EventsAfter {
590 fn new(offset: usize, reader: &LogController) -> EventsAfter {
591 EventsAfter {
592 next_item: None,
593 item_iterator: ItemsAfter::new(offset, &reader.path),
594 }
595 }
596 fn get_end_time(&mut self) -> Option<NaiveDateTime> {
597 self.next_item = None;
598 loop {
599 if let Some(i) = self.item_iterator.next() {
600 match i {
601 Item::Event(e, _) => {
602 let time = e.start.clone();
603 self.next_item = Some(e);
604 return Some(time);
605 }
606 Item::Done(d, _) => return Some(d.0),
607 _ => (),
608 }
609 } else {
610 return None;
611 }
612 }
613 }
614}
615
616impl Iterator for EventsAfter {
617 type Item = Event;
618 fn next(&mut self) -> Option<Event> {
619 if let Some(event) = &self.next_item {
620 return Some(event.clone().bounded_time(self.get_end_time()));
621 }
622 loop {
623 if let Some(i) = self.item_iterator.next() {
624 match i {
625 Item::Event(e, _) => return Some(e.bounded_time(self.get_end_time())),
626 _ => (),
627 }
628 } else {
629 return None;
630 }
631 }
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use chrono::Duration;
639 use rand::seq::SliceRandom;
640 use rand::{thread_rng, Rng};
641 use std::fs::File;
642 use std::io::LineWriter;
643 use std::ops::AddAssign;
644 use std::str::FromStr;
645
646 enum Need {
647 E,
648 N,
649 B,
650 C,
651 Error,
652 }
653
654 fn random_tag() -> String {
655 let choices = ["foo", "bar", "baz", "plugh", "work", "play", "tedium"];
656 choices[rand::thread_rng().gen_range(0, choices.len())].to_owned()
657 }
658
659 fn random_words(min: usize, max: usize) -> Vec<String> {
660 (0..(rand::thread_rng().gen_range(min, max + 1)))
661 .map(|_| random_tag())
662 .collect()
663 }
664
665 fn random_tags() -> Vec<String> {
666 let mut tags = random_words(0, 5);
667 tags.sort_unstable();
668 tags.dedup();
669 tags
670 }
671
672 fn random_text() -> String {
673 let mut words = random_words(5, 15);
674 let mut word = words.remove(0);
675 for w in words {
676 word += " ";
677 word.push_str(&w);
678 }
679 word
680 }
681
682 fn random_line(
683 time: &mut NaiveDateTime,
684 open_event: bool,
685 offset: usize,
686 need: Option<Need>,
687 ) -> Item {
688 let n = rand::thread_rng().gen_range(0, 100);
689 let need = if let Some(need) = need {
690 need
691 } else {
692 if n < 4 {
693 Need::B
694 } else if n < 10 {
695 Need::C
696 } else if n < 11 {
697 Need::Error
698 } else if n < 20 {
699 Need::N
700 } else {
701 Need::E
702 }
703 };
704 match need {
705 Need::B => Item::Blank(offset),
706 Need::C => {
707 let mut comment = String::from("# ");
708 comment.push_str(&random_text());
709 Item::Comment(offset)
710 }
711 Need::Error => Item::Error(random_text(), offset),
712 Need::N => {
713 time.add_assign(Duration::seconds(rand::thread_rng().gen_range(1, 1000)));
714 Item::Note(
715 Note {
716 time: time.clone(),
717 description: random_text(),
718 tags: random_tags(),
719 },
720 offset,
721 )
722 }
723 Need::E => {
724 time.add_assign(Duration::seconds(rand::thread_rng().gen_range(1, 1000)));
725 if open_event && n < 30 {
726 Item::Done(Done(time.clone()), offset)
727 } else {
728 Item::Event(
729 Event {
730 start: time.clone(),
731 start_overlap: false,
732 end: None,
733 end_overlap: false,
734 tags: random_tags(),
735 description: random_text(),
736 vacation: false,
737 vacation_type: None,
738 },
739 offset,
740 )
741 }
742 }
743 }
744 }
745
746 fn random_log(length: usize, need: Vec<Need>, disambiguator: &str) -> (Vec<Item>, String) {
748 let mut initial_time = NaiveDate::from_ymd(2019, 12, 22).and_hms(9, 39, 30);
749 let mut items: Vec<Item> = Vec::with_capacity(length);
750 let mut open_event = false;
751 let path = format!(
755 "{}-{}-{}.log",
756 disambiguator,
757 length,
758 Local::now().naive_local().timestamp_millis()
759 );
760 let file = File::create(path.clone()).unwrap();
761 let mut file = LineWriter::new(file);
762 let mut need: Vec<(usize, Need)> = if need.is_empty() {
763 vec![]
764 } else {
765 let mut indices: Vec<usize> = (0..length).collect();
767 indices.shuffle(&mut thread_rng());
768 let mut need = need;
769 need.shuffle(&mut thread_rng());
770 let mut need = need
771 .into_iter()
772 .map(|n| (indices.remove(0), n))
773 .collect::<Vec<_>>();
774 need.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
775 need
776 };
777 for offset in 0..length {
778 let t = if let Some((i, _)) = need.get(0) {
779 if i == &offset {
780 let t = need.remove(0).1;
781 Some(t)
782 } else {
783 None
784 }
785 } else {
786 None
787 };
788 let item = random_line(&mut initial_time, open_event, offset, t);
789 open_event = match item {
790 Item::Done(_, _) => false,
791 Item::Event(_, _) => true,
792 _ => open_event,
793 };
794 let line = match &item {
795 Item::Event(e, _) => e.to_line(),
796 Item::Note(n, _) => n.to_line(),
797 Item::Done(d, _) => d.to_line(),
798 Item::Blank(_) => String::new(),
799 Item::Comment(_) => {
800 let mut s = String::from("# ");
801 s.push_str(&random_text());
802 s
803 }
804 Item::Error(s, _) => s.clone(),
805 };
806 file.write_all(line.as_ref()).unwrap();
807 file.write_all("\n".as_ref()).unwrap();
808 if item.has_time() {
809 items.push(item);
810 }
811 }
812 (items, path)
813 }
814
815 fn closed_events(mut items: Vec<Item>) -> Vec<Event> {
816 items.reverse();
817 let mut ret = Vec::with_capacity(items.len());
818 let mut last_time: Option<NaiveDateTime> = None;
819 for i in items.iter() {
820 match i {
821 Item::Done(Done(t), _) => last_time = Some(t.clone()),
822 Item::Event(e, _) => {
823 let mut e = e.clone();
824 if last_time.is_some() {
825 e.end = last_time;
826 }
827 last_time = Some(e.start.clone());
828 ret.push(e);
829 }
830 _ => (),
831 }
832 }
833 ret.reverse();
834 ret
835 }
836
837 fn notes(items: Vec<Item>) -> Vec<Note> {
838 let mut ret = Vec::with_capacity(items.len());
839 for i in items.iter() {
840 match i {
841 Item::Note(n, _) => {
842 ret.push(n.clone());
843 }
844 _ => (),
845 }
846 }
847 ret
848 }
849
850 fn test_configuration(path: &str) -> (String, Configuration) {
851 let conf_path = format!("{}_conf", path);
852 File::create(
853 PathBuf::from_str(&conf_path)
854 .expect(&format!("could not create path {}", conf_path))
855 .as_path(),
856 )
857 .expect(&format!("could not create file {}", conf_path));
858 let pb = PathBuf::from_str(&conf_path)
859 .expect(&format!("could not form path from {}", conf_path));
860 let conf = Configuration::read(Some(pb), None);
861 (conf_path, conf)
862 }
863
864 fn cleanup(paths: &[&str]) {
865 for p in paths {
866 let pb = PathBuf::from_str(p).expect(&format!("cannot form a path from {}", p));
867 if pb.as_path().exists() {
868 std::fs::remove_file(p).expect(&format!("failed to remove {}", p))
869 }
870 }
871 }
872
873 #[test]
874 fn test_notes_in_range() {
875 let (items, path) = random_log(100, vec![Need::N, Need::N], "test_notes_in_range");
876 let notes = notes(items);
877 assert!(notes.len() > 1, "found more than one note");
878 let (conf_path, conf) = test_configuration("test_notes_in_range");
879 let mut log_reader =
880 LogController::new(Some(PathBuf::from_str(&path).unwrap()), &conf).unwrap();
881 for i in 0..notes.len() - 1 {
882 for j in i..notes.len() {
883 let found_notes = log_reader.notes_in_range(¬es[i].time, ¬es[j].time);
884 assert!(
885 j - i == found_notes.len(),
886 "found as many events as expected"
887 );
888 for offset in 0..found_notes.len() {
889 let k = i + offset;
890 assert_eq!(notes[k].time, found_notes[offset].time, "same time");
891 assert_eq!(notes[k].tags, found_notes[offset].tags, "same tags");
892 assert_eq!(
893 notes[k].description, found_notes[offset].description,
894 "same description"
895 );
896 }
897 }
898 }
899 cleanup(&[&path, &conf_path]);
900 }
901
902 #[test]
903 fn test_events_in_range() {
904 let (items, path) = random_log(20, vec![Need::E, Need::E], "test_events_in_range");
905 let events = closed_events(items);
906 assert!(events.len() > 1, "found more than one event");
907 let (conf_path, conf) = test_configuration("test_events_in_range");
908 let mut log_reader =
909 LogController::new(Some(PathBuf::from_str(&path).unwrap()), &conf).unwrap();
910 for i in 0..events.len() - 1 {
911 for j in i..events.len() {
912 let found_events = log_reader.events_in_range(&events[i].start, &events[j].start);
913 assert!(
914 j - i <= found_events.len(),
915 "found at least as many events as expected"
916 );
917 for offset in 0..found_events.len() {
918 let k = i + offset;
919 assert_eq!(events[k].start, found_events[offset].start, "same start");
920 assert_eq!(events[k].end, found_events[offset].end, "same end");
921 assert_eq!(events[k].tags, found_events[offset].tags, "same tags");
922 assert_eq!(
923 events[k].description, found_events[offset].description,
924 "same description"
925 );
926 }
927 }
928 }
929 cleanup(&[&path, &conf_path]);
930 }
931
932 #[test]
933 fn test_notes_from_end() {
934 let (items, path) = random_log(100, vec![Need::N], "test_notes_from_end");
935 let mut notes = notes(items);
936 notes.reverse();
937 let (conf_path, conf) = test_configuration("test_notes_from_end");
938 let mut log_reader =
939 LogController::new(Some(PathBuf::from_str(&path).unwrap()), &conf).unwrap();
940 let found_notes = log_reader.notes_from_the_end().collect::<Vec<_>>();
941 assert_eq!(
942 notes.len(),
943 found_notes.len(),
944 "found the right number of notes"
945 );
946 for (i, e) in notes.iter().enumerate() {
947 assert_eq!(e.time, found_notes[i].time, "they occur at the same time");
948 assert_eq!(e.tags, found_notes[i].tags, "they have the same tags");
949 assert_eq!(
950 e.description, found_notes[i].description,
951 "they have the same text"
952 );
953 }
954 cleanup(&[&path, &conf_path]);
955 }
956
957 #[test]
958 fn test_notes_from_beginning() {
959 let (items, path) = random_log(103, vec![Need::N], "test_notes_from_beginning");
960 let notes = notes(items);
961 let (conf_path, conf) = test_configuration("test_notes_from_beginning");
962 let log_reader =
963 LogController::new(Some(PathBuf::from_str(&path).unwrap()), &conf).unwrap();
964 let found_notes = log_reader.notes_from_the_beginning().collect::<Vec<_>>();
965 assert_eq!(
966 notes.len(),
967 found_notes.len(),
968 "found the right number of notes"
969 );
970 for (i, n) in notes.iter().enumerate() {
971 assert_eq!(n.time, found_notes[i].time, "they occur at the same time");
972 assert_eq!(n.tags, found_notes[i].tags, "they have the same tags");
973 assert_eq!(
974 n.description, found_notes[i].description,
975 "they have the same text"
976 );
977 }
978 cleanup(&[&path, &conf_path]);
979 }
980
981 #[test]
982 fn test_events_from_end() {
983 let (items, path) = random_log(107, vec![Need::E], "test_events_from_end");
984 let mut events = closed_events(items);
985 events.reverse();
986 let (conf_path, conf) = test_configuration("test_events_from_end");
987 let mut log_reader =
988 LogController::new(Some(PathBuf::from_str(&path).unwrap()), &conf).unwrap();
989 let found_events = log_reader.events_from_the_end().collect::<Vec<_>>();
990 assert_eq!(
991 events.len(),
992 found_events.len(),
993 "found the right number of events"
994 );
995 for (i, e) in events.iter().enumerate() {
996 assert_eq!(
997 e.start, found_events[i].start,
998 "they start at the same time"
999 );
1000 assert_eq!(e.end, found_events[i].end, "they end at the same time");
1001 assert_eq!(e.tags, found_events[i].tags, "they have the same tags");
1002 assert_eq!(
1003 e.description, found_events[i].description,
1004 "they have the same description"
1005 );
1006 }
1007 cleanup(&[&path, &conf_path]);
1008 }
1009
1010 #[test]
1011 fn test_events_from_beginning() {
1012 let (items, path) = random_log(100, vec![Need::E], "test_events_from_beginning");
1013 let events = closed_events(items);
1014 let (conf_path, conf) = test_configuration("test_events_from_beginning");
1015 let log_reader =
1016 LogController::new(Some(PathBuf::from_str(&path).unwrap()), &conf).unwrap();
1017 let found_events = log_reader.events_from_the_beginning().collect::<Vec<_>>();
1018 assert_eq!(
1019 events.len(),
1020 found_events.len(),
1021 "found the right number of events"
1022 );
1023 for (i, e) in events.iter().enumerate() {
1024 assert_eq!(
1025 e.start, found_events[i].start,
1026 "they start at the same time"
1027 );
1028 assert_eq!(e.end, found_events[i].end, "they end at the same time");
1029 assert_eq!(e.tags, found_events[i].tags, "they have the same tags");
1030 assert_eq!(
1031 e.description, found_events[i].description,
1032 "they have the same description"
1033 );
1034 }
1035 cleanup(&[&path, &conf_path]);
1036 }
1037
1038 fn test_log(length: usize, disambiguator: &str) {
1039 let (items, path) = random_log(length, vec![], disambiguator);
1040 if items.is_empty() {
1041 println!("empty file; skipping...");
1042 } else {
1043 let (conf_path, conf) = test_configuration(&path);
1044 let mut log_reader =
1045 LogController::new(Some(PathBuf::from_str(&path).unwrap()), &conf).unwrap();
1046 let mut last_timed_item: Option<Item> = None;
1047 for item in items {
1048 let (time, offset) = item.time().unwrap();
1049 let found_item = log_reader.find_line(time);
1050 if let Some(found_item) = found_item {
1051 assert_eq!(offset, found_item.offset());
1052 if let Some(lti) = last_timed_item.clone() {
1053 let (t1, _) = lti.time().unwrap();
1054 let (t2, _) = found_item.time().unwrap();
1055 let d = *t2 - *t1;
1056 if d.num_seconds() > 1 {
1057 let intermediate_time = t1
1058 .checked_add_signed(Duration::seconds(d.num_seconds() / 2))
1059 .unwrap();
1060 let should_be_found = log_reader.find_line(&intermediate_time);
1061 if let Some(should_be_found) = should_be_found {
1062 assert_eq!(last_timed_item.unwrap(), should_be_found);
1063 } else {
1064 assert!(false, "failed to revert to found time when looking for missing intermediate time {}", intermediate_time);
1065 }
1066 }
1067 }
1068 last_timed_item = Some(found_item);
1069 } else {
1070 assert!(false, "could not find item at offset {}", offset);
1071 }
1072 cleanup(&[&conf_path]);
1073 }
1074 }
1075 cleanup(&[&path]);
1076 }
1077
1078 #[test]
1079 fn test_empty_file() {
1080 test_log(0, "test_empty_file");
1081 }
1082
1083 #[test]
1084 fn test_100_tiny_files() {
1085 for i in 0..100 {
1086 test_log(5, &format!("test_100_tiny_files_{}", i));
1087 }
1088 }
1089
1090 #[test]
1091 fn test_10_small_files() {
1092 for i in 0..10 {
1093 test_log(100, &format!("test_10_small_files_{}", i));
1094 }
1095 }
1096
1097 #[test]
1098 fn test_large_file() {
1099 test_log(10000, "test_large_file");
1100 }
1101
1102 #[test]
1103 fn test_event() {
1104 match parse_line("2019 12 1 16 3 30::an event with no tags", 0) {
1105 Item::Event(
1106 Event {
1107 start,
1108 tags,
1109 description,
1110 ..
1111 },
1112 _,
1113 ) => {
1114 assert_eq!(2019, start.year());
1115 assert_eq!(12, start.month());
1116 assert_eq!(1, start.day());
1117 assert_eq!(16, start.hour());
1118 assert_eq!(3, start.minute());
1119 assert_eq!(30, start.second());
1120 assert!(tags.is_empty(), "there are no tags");
1121 assert_eq!(
1122 "an event with no tags", &description,
1123 "got correct description"
1124 )
1125 }
1126 _ => assert!(false, "failed to parse an event line"),
1127 };
1128 match parse_line("2019 12 1 16 3 30:foo bar:an event with some tags", 0) {
1129 Item::Event(
1130 Event {
1131 start,
1132 tags,
1133 description,
1134 ..
1135 },
1136 _,
1137 ) => {
1138 assert_eq!(2019, start.year());
1139 assert_eq!(12, start.month());
1140 assert_eq!(1, start.day());
1141 assert_eq!(16, start.hour());
1142 assert_eq!(3, start.minute());
1143 assert_eq!(30, start.second());
1144 assert_eq!(2, tags.len(), "there are some tags");
1145 for t in vec!["foo", "bar"] {
1146 assert!(tags.contains(&t.to_owned()));
1147 }
1148 assert_eq!(
1149 "an event with some tags", &description,
1150 "got correct description"
1151 )
1152 }
1153 _ => assert!(false, "failed to parse an event line"),
1154 };
1155 match parse_line("2019 12 1 16 3 30:foo\\ bar:an event with some tags", 0) {
1157 Item::Event(
1158 Event {
1159 start,
1160 tags,
1161 description,
1162 ..
1163 },
1164 _,
1165 ) => {
1166 assert_eq!(2019, start.year());
1167 assert_eq!(12, start.month());
1168 assert_eq!(1, start.day());
1169 assert_eq!(16, start.hour());
1170 assert_eq!(3, start.minute());
1171 assert_eq!(30, start.second());
1172 assert_eq!(1, tags.len(), "there are some tags");
1173 for t in vec!["foo bar"] {
1174 assert!(tags.contains(&t.to_owned()));
1175 }
1176 assert_eq!(
1177 "an event with some tags", &description,
1178 "got correct description"
1179 )
1180 }
1181 _ => assert!(false, "failed to parse an event line"),
1182 };
1183 match parse_line("2019 12 22 12 49 24:foo:plugh baz baz foo play play work baz tedium foo tedium foo work bar", 0) {
1185 Item::Event(
1186 Event {
1187 start,
1188 tags,
1189 description,
1190 ..
1191 },
1192 _,
1193 ) => {
1194 assert_eq!(2019, start.year());
1195 assert_eq!(12, start.month());
1196 assert_eq!(22, start.day());
1197 assert_eq!(12, start.hour());
1198 assert_eq!(49, start.minute());
1199 assert_eq!(24, start.second());
1200 assert_eq!(1, tags.len(), "there are some tags");
1201 for t in vec!["foo"] {
1202 assert!(tags.contains(&t.to_owned()));
1203 }
1204 assert_eq!(
1205 "plugh baz baz foo play play work baz tedium foo tedium foo work bar", &description,
1206 "got correct description"
1207 )
1208 }
1209 _ => assert!(false, "failed to parse an event line"),
1210 };
1211 }
1212
1213 #[test]
1214 fn test_note() {
1215 match parse_line("2019 12 1 16 3 30<NOTE>:a note with no tags", 0) {
1216 Item::Note(
1217 Note {
1218 time,
1219 tags,
1220 description,
1221 },
1222 _,
1223 ) => {
1224 assert_eq!(2019, time.year());
1225 assert_eq!(12, time.month());
1226 assert_eq!(1, time.day());
1227 assert_eq!(16, time.hour());
1228 assert_eq!(3, time.minute());
1229 assert_eq!(30, time.second());
1230 assert!(tags.is_empty(), "there are no tags");
1231 assert_eq!(
1232 "a note with no tags", &description,
1233 "got correct description"
1234 )
1235 }
1236 _ => assert!(false, "failed to parse a NOTE line"),
1237 };
1238 match parse_line("2019 12 1 16 3 30<NOTE>foo bar:a short note", 0) {
1239 Item::Note(
1240 Note {
1241 time,
1242 tags,
1243 description,
1244 },
1245 _,
1246 ) => {
1247 assert_eq!(2019, time.year());
1248 assert_eq!(12, time.month());
1249 assert_eq!(1, time.day());
1250 assert_eq!(16, time.hour());
1251 assert_eq!(3, time.minute());
1252 assert_eq!(30, time.second());
1253 assert_eq!(tags.len(), 2, "there are two tags");
1254 for t in vec!["foo", "bar"] {
1255 assert!(tags.contains(&t.to_owned()));
1256 }
1257 assert_eq!("a short note", &description, "got correct description")
1258 }
1259 _ => assert!(false, "failed to parse a NOTE line"),
1260 };
1261 match parse_line(
1262 r"2019 12 1 16 3 30<NOTE>f\:oo b\<ar b\ az pl\\ugh:a short note",
1263 0,
1264 ) {
1265 Item::Note(
1266 Note {
1267 time,
1268 tags,
1269 description,
1270 },
1271 _,
1272 ) => {
1273 assert_eq!(2019, time.year());
1274 assert_eq!(12, time.month());
1275 assert_eq!(1, time.day());
1276 assert_eq!(16, time.hour());
1277 assert_eq!(3, time.minute());
1278 assert_eq!(30, time.second());
1279 assert_eq!(tags.len(), 4, "there are two tags");
1280 for t in vec!["f:oo", "b<ar", "b az", r"pl\ugh"] {
1281 assert!(tags.contains(&t.to_owned()), "escaping worked");
1282 }
1283 assert_eq!("a short note", &description, "got correct description")
1284 }
1285 _ => assert!(false, "failed to parse a NOTE line"),
1286 };
1287 match parse_line("2019 12 1 16 3 30<NOTE>foo bar bar:a short note", 0) {
1288 Item::Note(
1289 Note {
1290 time,
1291 tags,
1292 description,
1293 },
1294 _,
1295 ) => {
1296 assert_eq!(2019, time.year());
1297 assert_eq!(12, time.month());
1298 assert_eq!(1, time.day());
1299 assert_eq!(16, time.hour());
1300 assert_eq!(3, time.minute());
1301 assert_eq!(30, time.second());
1302 assert_eq!(tags.len(), 2, "there are two tags");
1303 for t in vec!["foo", "bar"] {
1304 assert!(tags.contains(&t.to_owned()));
1305 }
1306 assert_eq!("a short note", &description, "got correct description")
1307 }
1308 _ => assert!(false, "failed to parse a NOTE line"),
1309 };
1310 match parse_line("2019 12 1 16 3 30<NOTE> foo bar :a short note", 0) {
1311 Item::Note(
1312 Note {
1313 time,
1314 tags,
1315 description,
1316 },
1317 _,
1318 ) => {
1319 assert_eq!(2019, time.year());
1320 assert_eq!(12, time.month());
1321 assert_eq!(1, time.day());
1322 assert_eq!(16, time.hour());
1323 assert_eq!(3, time.minute());
1324 assert_eq!(30, time.second());
1325 assert_eq!(tags.len(), 2, "there are two tags");
1326 for t in vec!["foo", "bar"] {
1327 assert!(tags.contains(&t.to_owned()));
1328 }
1329 assert_eq!("a short note", &description, "got correct description")
1330 }
1331 _ => assert!(false, "failed to parse a NOTE line"),
1332 };
1333 match parse_line("2019 12 22 9 59 34<NOTE>foo play tedium work:baz tedium baz tedium foo plugh bar foo bar play plugh foo baz play baz tedium work work play play bar", 0) {
1335 Item::Note(
1336 Note {
1337 time,
1338 tags,
1339 description,
1340 },
1341 _,
1342 ) => {
1343 assert_eq!(2019, time.year());
1344 assert_eq!(12, time.month());
1345 assert_eq!(22, time.day());
1346 assert_eq!(9, time.hour());
1347 assert_eq!(59, time.minute());
1348 assert_eq!(34, time.second());
1349 assert_eq!(tags.len(), 4, "there are three tags");
1350 for t in vec!["foo", "play", "tedium", "work"] {
1351 assert!(tags.contains(&t.to_owned()));
1352 }
1353 assert_eq!("baz tedium baz tedium foo plugh bar foo bar play plugh foo baz play baz tedium work work play play bar", &description, "got correct description")
1354 }
1355 _ => assert!(false, "failed to parse a NOTE line"),
1356 };
1357 match parse_line(
1359 "2019 12 22 12 8 0<NOTE>bar:tedium plugh baz play tedium baz play work",
1360 0,
1361 ) {
1362 Item::Note(
1363 Note {
1364 time,
1365 tags,
1366 description,
1367 },
1368 _,
1369 ) => {
1370 assert_eq!(2019, time.year());
1371 assert_eq!(12, time.month());
1372 assert_eq!(22, time.day());
1373 assert_eq!(12, time.hour());
1374 assert_eq!(8, time.minute());
1375 assert_eq!(0, time.second());
1376 assert_eq!(tags.len(), 1, "there is one tag");
1377 for t in vec!["bar"] {
1378 assert!(tags.contains(&t.to_owned()));
1379 }
1380 assert_eq!(
1381 "tedium plugh baz play tedium baz play work", &description,
1382 "got correct description"
1383 )
1384 }
1385 _ => assert!(false, "failed to parse a NOTE line"),
1386 };
1387 }
1388
1389 #[test]
1390 fn test_done() {
1391 match parse_line("2019 12 1 16 3 30:DONE", 0) {
1392 Item::Done(Done(time), _) => {
1393 assert_eq!(2019, time.year());
1394 assert_eq!(12, time.month());
1395 assert_eq!(1, time.day());
1396 assert_eq!(16, time.hour());
1397 assert_eq!(3, time.minute());
1398 assert_eq!(30, time.second());
1399 }
1400 _ => assert!(false, "failed to parse a DONE line"),
1401 };
1402 match parse_line(" 2019 12 1 16 3 30 :DONE", 0) {
1403 Item::Done(Done(time), _) => {
1404 assert_eq!(2019, time.year(), "space doesn't matter");
1405 assert_eq!(12, time.month(), "space doesn't matter");
1406 assert_eq!(1, time.day(), "space doesn't matter");
1407 assert_eq!(16, time.hour(), "space doesn't matter");
1408 assert_eq!(3, time.minute(), "space doesn't matter");
1409 assert_eq!(30, time.second(), "space doesn't matter");
1410 }
1411 _ => assert!(false, "failed to parse a DONE line"),
1412 };
1413 }
1414
1415 #[test]
1416 fn test_tag_whitespace_handling() {
1417 let e = Event::coin(
1418 String::from("foo"),
1419 vec![String::from("foo bar"), String::from("baz plugh")],
1420 );
1421 match parse_line(e.to_line().as_str(), 0) {
1422 Item::Event(Event { tags, .. }, _) => {
1423 assert_eq!(2, tags.len());
1424 assert!(tags.contains(&String::from("foo bar")));
1425 assert!(tags.contains(&String::from("baz plugh")));
1426 }
1427 _ => assert!(false, "failed to parse line as an event"),
1428 }
1429 }
1430
1431 #[test]
1432 fn test_zero_padding() {
1433 match parse_line("2019 12 01 16 03 30:DONE", 0) {
1434 Item::Done(Done(time), _) => {
1435 assert_eq!(1, time.day());
1436 assert_eq!(3, time.minute());
1437 }
1438 _ => assert!(false, "failed to parse a DONE line"),
1439 };
1440 }
1441
1442 #[test]
1443 fn test_comment() {
1444 let success = match parse_line("#foo", 0) {
1445 Item::Comment(_) => true,
1446 _ => false,
1447 };
1448 assert!(success, "recognized '#foo' as a comment line");
1449 let success = match parse_line(" #foo", 0) {
1450 Item::Comment(_) => true,
1451 _ => false,
1452 };
1453 assert!(success, "comments can have leading space");
1454 }
1455
1456 #[test]
1457 fn test_error() {
1458 let success = match parse_line("foo", 0) {
1459 Item::Error(_, _) => true,
1460 _ => false,
1461 };
1462 assert!(success, "recognized 'foo' as a malformed log line");
1463 }
1464
1465 #[test]
1466 fn test_blank() {
1467 let success = match parse_line("", 0) {
1468 Item::Blank(_) => true,
1469 _ => false,
1470 };
1471 assert!(success, "recognized an empty line as a blank");
1472 let success = match parse_line(" ", 0) {
1473 Item::Blank(_) => true,
1474 _ => false,
1475 };
1476 assert!(success, "recognized a whitespace line as a blank");
1477 }
1478
1479 #[test]
1480 fn stack_overflow_regression() {
1481 let (items, path) = random_log(23, vec![Need::E, Need::E], "stack_overflow_regression");
1482 let events = closed_events(items);
1483 assert!(events.len() > 1, "found more than one event");
1484 let (conf_path, conf) = test_configuration("stack_overflow_regression");
1485 let mut log_reader =
1486 LogController::new(Some(PathBuf::from_str(&path).unwrap()), &conf).unwrap();
1487 let e = events.first().unwrap();
1488 let false_start = e.start - Duration::days(1);
1489 let found_events = log_reader.events_in_range(&false_start, e.end.as_ref().unwrap());
1490 assert_eq!(1, found_events.len(), "found one event");
1491 assert_eq!(e.start, found_events[0].start, "same start");
1492 assert_eq!(e.end, found_events[0].end, "same end");
1493 assert_eq!(e.tags, found_events[0].tags, "same tags");
1494 assert_eq!(
1495 e.description, found_events[0].description,
1496 "same description"
1497 );
1498 cleanup(&[&path, &conf_path]);
1499 }
1500}
1501
1502#[derive(Debug, Clone)]
1504pub enum Item {
1505 Event(Event, usize),
1506 Note(Note, usize),
1507 Done(Done, usize),
1508 Blank(usize),
1509 Comment(usize),
1510 Error(String, usize),
1511}
1512
1513impl Item {
1514 fn advance(&self, time: &NaiveDateTime) -> NaiveDateTime {
1515 match self {
1516 Item::Event(e, _) => {
1517 if time < &e.start {
1518 e.start.clone()
1519 } else {
1520 time.clone()
1521 }
1522 }
1523 Item::Note(n, _) => {
1524 if time < &n.time {
1525 n.time.clone()
1526 } else {
1527 time.clone()
1528 }
1529 }
1530 Item::Done(d, _) => {
1531 if time < &d.0 {
1532 d.0.clone()
1533 } else {
1534 time.clone()
1535 }
1536 }
1537 _ => time.clone(),
1538 }
1539 }
1540 pub fn time(&self) -> Option<(&NaiveDateTime, usize)> {
1541 match self {
1542 Item::Event(e, offset) => Some((&e.start, *offset)),
1543 Item::Note(n, offset) => Some((&n.time, *offset)),
1544 Item::Done(d, offset) => Some((&d.0, *offset)),
1545 _ => None,
1546 }
1547 }
1548 pub fn has_time(&self) -> bool {
1549 match self {
1550 Item::Event(_, _) | Item::Note(_, _) | Item::Done(_, _) => true,
1551 _ => false,
1552 }
1553 }
1554 pub fn offset(&self) -> usize {
1556 match self {
1557 Item::Event(_, i) => *i,
1558 Item::Note(_, i) => *i,
1559 Item::Done(_, i) => *i,
1560 Item::Blank(i) => *i,
1561 Item::Comment(i) => *i,
1562 Item::Error(_, i) => *i,
1563 }
1564 }
1565}
1566
1567impl PartialEq for Item {
1568 fn eq(&self, other: &Item) -> bool {
1569 self.offset() == other.offset()
1570 }
1571}
1572
1573impl PartialOrd for Item {
1574 fn partial_cmp(&self, other: &Item) -> Option<std::cmp::Ordering> {
1575 self.offset().partial_cmp(&other.offset())
1576 }
1577}
1578
1579pub fn parse_timestamp(timestamp: &str) -> Result<NaiveDateTime, String> {
1580 lazy_static! {
1581 static ref RE: Regex = Regex::new(r"\d+").unwrap();
1582 }
1583 let numbers: Vec<_> = RE.find_iter(timestamp).map(|m| m.as_str()).collect();
1584 let year = numbers[0].parse::<i32>().unwrap();
1586 let month = numbers[1].parse::<u32>().unwrap();
1587 if month == 0 || month > 12 {
1588 return Err(format!("bad month: {}; must be in the range 1-12", month));
1589 }
1590 let day = numbers[2].parse::<u32>().unwrap();
1591 if day == 0 || day > 31 {
1592 return Err(format!("bad day: {}; day must be in the range 1-31", day));
1593 }
1594 let hour = numbers[3].parse::<u32>().unwrap();
1595 if hour > 23 {
1596 return Err(format!("bad hour: {}; hour must be less than 24", hour));
1597 }
1598 let minute = numbers[4].parse::<u32>().unwrap();
1599 if minute > 59 {
1600 return Err(format!(
1601 "bad minute: {}; minute must be less than 60",
1602 minute
1603 ));
1604 }
1605 let second = numbers[5].parse::<u32>().unwrap();
1606 if second > 59 {
1607 return Err(format!(
1608 "bad second: {}; second must be less than 60",
1609 second
1610 ));
1611 }
1612 match NaiveDate::from_ymd_opt(year, month, day) {
1613 Some(date) => Ok(date.and_hms(hour, minute, second)),
1614 _ => Err(String::from("impossible date")),
1615 }
1616}
1617
1618pub fn timestamp(ts: &NaiveDateTime) -> String {
1619 format!(
1620 "{} {:>2} {:>2} {:>2} {:>2} {:>2}",
1621 ts.year(),
1622 ts.month(),
1623 ts.day(),
1624 ts.hour(),
1625 ts.minute(),
1626 ts.second()
1627 )
1628}
1629
1630pub fn parse_tags(tags: &str) -> Vec<String> {
1632 let mut parsed = vec![];
1633 let mut escaped = false;
1634 let mut current = String::with_capacity(tags.len());
1635 for c in tags.chars() {
1636 if c == '\\' {
1637 if escaped {
1638 current.push(c);
1639 } else {
1640 escaped = true;
1641 }
1642 } else if c == ' ' {
1643 if escaped {
1645 current.push(c);
1646 } else {
1647 if current.len() > 0 && !parsed.contains(¤t) {
1648 parsed.push(current.clone());
1649 }
1650 current.clear();
1651 }
1652 escaped = false;
1653 } else {
1654 current.push(c);
1655 escaped = false;
1656 }
1657 }
1658 if current.len() > 0 && !parsed.contains(¤t) {
1659 parsed.push(current);
1660 }
1661 parsed
1662}
1663
1664pub fn tags(tags: &Vec<String>) -> String {
1666 let mut v = tags.clone();
1667 v.sort_unstable();
1668 v.dedup(); let mut s = String::new();
1670 for (i, tag) in v.iter().enumerate() {
1671 if i > 0 {
1672 s.push(' ');
1673 }
1674 let mut ws = false;
1675 for c in tag.chars() {
1676 match c {
1677 ':' | '\\' | '<' => s.push('\\'),
1678 _ => (),
1679 }
1680 if c.is_whitespace() {
1681 if !ws {
1682 ws = true;
1683 s.push('\\');
1684 s.push(' '); }
1686 } else {
1687 ws = false;
1688 s.push(c);
1689 }
1690 }
1691 }
1692 s
1693}
1694
1695#[derive(Debug, Clone)]
1696pub struct Event {
1697 pub start: NaiveDateTime,
1698 pub start_overlap: bool,
1699 pub end: Option<NaiveDateTime>,
1700 pub end_overlap: bool,
1701 pub description: String,
1702 pub tags: Vec<String>,
1703 pub vacation: bool,
1704 pub vacation_type: Option<String>,
1705}
1706
1707impl Event {
1708 pub fn coin(description: String, mut tags: Vec<String>) -> Event {
1709 tags.sort_unstable();
1710 tags.dedup();
1711 Event {
1712 start: Local::now().naive_local(),
1713 start_overlap: false,
1714 end: None,
1715 end_overlap: false,
1716 description: description,
1717 tags: tags,
1718 vacation: false,
1719 vacation_type: None,
1720 }
1721 }
1722 fn bounded_time(self, end: Option<NaiveDateTime>) -> Self {
1723 Event {
1724 start: self.start,
1725 start_overlap: self.start_overlap,
1726 end: end,
1727 end_overlap: self.end_overlap,
1728 description: self.description,
1729 tags: self.tags,
1730 vacation: self.vacation,
1731 vacation_type: self.vacation_type,
1732 }
1733 }
1734 pub fn ongoing(&self) -> bool {
1735 self.end.is_none()
1736 }
1737 pub fn duration(&self, now: &NaiveDateTime) -> f32 {
1740 let end = self.end.as_ref().unwrap_or(now);
1741 (end.timestamp() - self.start.timestamp()) as f32
1742 }
1743 fn split(self, time: NaiveDateTime) -> (Self, Self) {
1745 assert!(time > self.start);
1746 assert!(self.end.is_none() || self.end.unwrap() > time);
1747 let mut start = self;
1748 let mut end = start.clone();
1749 start.end_overlap = true;
1750 start.end = Some(time.clone());
1751 end.start = time;
1752 end.end_overlap = true;
1753 (start, end)
1754 }
1755 pub fn gather_by_day(events: Vec<Event>, end_date: &NaiveDateTime) -> Vec<Event> {
1757 let mut ret = vec![];
1758 let mut end_date = end_date;
1759 let now = Local::now().naive_local(); if &now < &end_date {
1761 end_date = &now;
1762 }
1763 for mut e in events {
1764 if &e.start >= end_date {
1765 break;
1766 }
1767 loop {
1768 match e.end.as_ref() {
1769 Some(&time) => {
1770 if time.date() == e.start.date() {
1771 ret.push(e);
1772 break;
1773 }
1774 let split_date = e.start.date().and_hms(0, 0, 0) + Duration::days(1);
1775 let (e1, e2) = e.split(split_date);
1776 e = e2;
1777 ret.push(e1);
1778 }
1779 None => {
1780 if e.start.date() == end_date.date() {
1781 ret.push(e);
1782 break;
1783 } else {
1784 let split_date = e.start.date().and_hms(0, 0, 0) + Duration::days(1);
1785 let (e1, e2) = e.split(split_date);
1786 e = e2;
1787 ret.push(e1);
1788 }
1789 }
1790 }
1791 }
1792 }
1793 ret
1794 }
1795 fn mergeable(&self, other: &Self) -> bool {
1796 if self.end_overlap {
1797 return false;
1799 }
1800 if let Some(t) = self.end {
1801 t.day() == self.start.day() && t == other.start && self.tags == other.tags
1803 } else {
1804 false
1805 }
1806 }
1807 pub fn overlaps_start(&self) -> bool {
1810 self.end_overlap && self.start.hour() == 0
1811 }
1812 pub fn overlaps_end(&self) -> bool {
1815 if !self.end_overlap {
1816 return false;
1817 }
1818 if let Some(t) = self.end {
1819 t.day() != self.start.day()
1820 } else {
1821 false
1822 }
1823 }
1824 fn merge(&mut self, other: Self) {
1825 self.description = self.description.clone() + "; " + &other.description;
1826 self.end = other.end;
1827 self.end_overlap = other.end_overlap;
1828 }
1829 pub fn gather_by_day_and_merge(events: Vec<Event>, end_date: &NaiveDateTime) -> Vec<Event> {
1831 let mut events = Self::gather_by_day(events, end_date);
1832 if events.is_empty() {
1833 return events;
1834 }
1835 let mut ret = vec![];
1836 ret.push(events.remove(0));
1837 for e in events {
1838 let i = ret.len() - 1;
1839 if ret[i].mergeable(&e) {
1840 ret[i].merge(e);
1841 } else {
1842 ret.push(e);
1843 }
1844 }
1845 ret
1846 }
1847 pub fn to_json(&self, now: &NaiveDateTime, conf: &Configuration) -> String {
1848 let end = if let Some(time) = self.end {
1849 serde_json::to_string(&format!("{}", time)).unwrap()
1850 } else {
1851 "null".to_owned()
1852 };
1853 format!(
1854 r#"{{"type":"Event","start":{},"end":{},"duration":{},{}"tags":{},"description":{}}}"#,
1855 serde_json::to_string(&format!("{}", self.start)).unwrap(),
1856 end,
1857 duration_string(self.duration(now), conf),
1858 if let Some(t) = &self.vacation_type {
1859 format!("\"vacation\":\"{}\",", if t == "" { "ordinary" } else { t })
1860 } else {
1861 "".to_owned()
1862 },
1863 serde_json::to_string(&self.tags).unwrap(),
1864 serde_json::to_string(&self.description).unwrap()
1865 )
1866 }
1867}
1868
1869impl Searchable for Event {
1870 fn text(&self) -> &str {
1871 &self.description
1872 }
1873 fn tags(&self) -> Vec<&str> {
1874 self.tags.iter().map(|s| s.as_str()).collect()
1875 }
1876}
1877
1878#[derive(Debug, Clone)]
1879pub struct Note {
1880 pub time: NaiveDateTime,
1881 pub description: String,
1882 pub tags: Vec<String>,
1883}
1884
1885impl Note {
1886 pub fn coin(description: String, mut tags: Vec<String>) -> Note {
1887 tags.sort_unstable();
1888 tags.dedup();
1889 Note {
1890 time: Local::now().naive_local(),
1891 description: description,
1892 tags: tags,
1893 }
1894 }
1895 pub fn to_json(&self, _now: &NaiveDateTime, _conf: &Configuration) -> String {
1896 format!(
1897 r#"{{"type":"Note","time":{},"tags":{},"description":{}}}"#,
1898 serde_json::to_string(&format!("{}", self.time)).unwrap(),
1899 serde_json::to_string(&self.tags).unwrap(),
1900 serde_json::to_string(&self.description).unwrap()
1901 )
1902 }
1903}
1904
1905impl Searchable for Note {
1906 fn text(&self) -> &str {
1907 &self.description
1908 }
1909 fn tags(&self) -> Vec<&str> {
1910 self.tags.iter().map(|s| s.as_str()).collect()
1911 }
1912}
1913
1914#[derive(Debug, Clone)]
1915pub struct Done(pub NaiveDateTime);
1916
1917impl Done {
1918 pub fn coin() -> Done {
1919 Done(Local::now().naive_local())
1920 }
1921}
1922
1923pub enum Direction {
1924 Forward,
1925 Back,
1926}
1927
1928pub trait LogLine {
1929 fn to_line(&self) -> String;
1930}
1931
1932impl LogLine for Done {
1933 fn to_line(&self) -> String {
1934 let mut ts = timestamp(&self.0);
1935 ts += ":DONE";
1936 ts
1937 }
1938}
1939
1940impl LogLine for Note {
1941 fn to_line(&self) -> String {
1942 let mut ts = timestamp(&self.time);
1943 ts += "<NOTE>";
1944 let tags = tags(&self.tags);
1945 ts += &tags;
1946 ts.push(':');
1947 ts += &self.description;
1948 ts
1949 }
1950}
1951
1952impl LogLine for Event {
1953 fn to_line(&self) -> String {
1954 let mut ts = timestamp(&self.start);
1955 ts.push(':');
1956 let tags = tags(&self.tags);
1957 ts += &tags;
1958 ts.push(':');
1959 ts += &self.description;
1960 ts
1961 }
1962}
1963
1964pub trait Searchable {
1965 fn tags(&self) -> Vec<&str>;
1966 fn text(&self) -> &str;
1967}
1968
1969pub struct Filter<'a> {
1970 all_tags: Option<Vec<&'a str>>,
1971 no_tags: Option<Vec<&'a str>>,
1972 some_tags: Option<Vec<&'a str>>,
1973 some_patterns: Option<RegexSet>,
1974 no_patterns: Option<RegexSet>,
1975 empty: bool,
1976}
1977
1978impl<'a> Filter<'a> {
1979 pub fn dummy() -> Filter<'a> {
1980 Filter {
1981 all_tags: None,
1982 no_tags: None,
1983 some_tags: None,
1984 some_patterns: None,
1985 no_patterns: None,
1986 empty: false,
1987 }
1988 }
1989 pub fn new(matches: &'a ArgMatches) -> Filter<'a> {
1990 let all_tags = matches
1991 .values_of("tag")
1992 .and_then(|values| Some(values.collect()));
1993 let no_tags = matches
1994 .values_of("tag-none")
1995 .and_then(|values| Some(values.collect()));
1996 let some_tags = matches
1997 .values_of("tag-some")
1998 .and_then(|values| Some(values.collect()));
1999 let some_patterns = matches
2000 .values_of("rx")
2001 .and_then(|values| Some(RegexSet::new(values).unwrap()));
2002 let no_patterns = matches
2003 .values_of("rx-not")
2004 .and_then(|values| Some(RegexSet::new(values).unwrap()));
2005 let empty = matches.is_present("no-tags");
2006 Filter {
2007 all_tags,
2008 no_tags,
2009 some_tags,
2010 some_patterns,
2011 no_patterns,
2012 empty,
2013 }
2014 }
2015 pub fn matches<T: Searchable>(&self, filterable: &T) -> bool {
2016 let tags = filterable.tags();
2017 let text = filterable.text();
2018 if tags.is_empty() {
2019 if self.empty {
2020 if let Some(rx_set) = self.some_patterns.as_ref() {
2021 if !rx_set.is_match(text) {
2022 return false;
2023 }
2024 }
2025 if let Some(rx_set) = self.no_patterns.as_ref() {
2026 if rx_set.is_match(text) {
2027 return false;
2028 }
2029 }
2030 return true;
2031 } else if !(self.all_tags.is_none() && self.some_tags.is_none()) {
2032 return false;
2033 }
2034 } else if self.empty {
2035 return false;
2036 } else {
2037 if self.some_tags.is_some()
2038 && !self
2039 .some_tags
2040 .as_ref()
2041 .unwrap()
2042 .iter()
2043 .any(|t| tags.contains(t))
2044 {
2045 return false;
2046 }
2047 if self.all_tags.is_some()
2048 && self
2049 .all_tags
2050 .as_ref()
2051 .unwrap()
2052 .iter()
2053 .any(|t| !tags.contains(t))
2054 {
2055 return false;
2056 }
2057 if self.no_tags.is_some()
2058 && self
2059 .no_tags
2060 .as_ref()
2061 .unwrap()
2062 .iter()
2063 .any(|t| tags.contains(t))
2064 {
2065 return false;
2066 }
2067 }
2068 if let Some(rx_set) = self.some_patterns.as_ref() {
2069 if !rx_set.is_match(text) {
2070 return false;
2071 }
2072 }
2073 if let Some(rx_set) = self.no_patterns.as_ref() {
2074 if rx_set.is_match(text) {
2075 return false;
2076 }
2077 }
2078 true
2079 }
2080}