timelog/entry/
kind.rs

1//! Module representing the kind of an entry.
2//!
3//! # Examples
4//!
5//! ```rust
6//! use timelog::entry::EntryKind;
7//!
8//! # fn main() {
9//!     let ek = EntryKind::new(Some('!'));
10//!     println!("Is ignored: {}", ek.is_ignored());
11//!
12//!     let ek = EntryKind::from_entry_line("2022-06-20 21:15:34^+happening Something");
13//!     println!("Is event: {}", ek.is_event());
14//! # }
15//! ```
16//!
17//! # Description
18//!
19//! Objects of this type represent the kind of an [`Entry`].
20
21#[cfg(doc)]
22use crate::entry::Entry;
23use crate::entry::{EntryError, MARKER_RE};
24
25/// Enumeration of different kinds of task entries
26#[derive(Debug, Clone, Copy, Eq, PartialEq)]
27pub enum EntryKind {
28    /// An entry that starts a task.
29    Start,
30    /// Stop the most current task.
31    Stop,
32    /// An entry that is ignored.
33    Ignored,
34    /// An entry that marks a zero duration event.
35    Event
36}
37
38impl From<EntryKind> for char {
39    /// Return the matching mark character for the supplied kind.
40    fn from(k: EntryKind) -> char {
41        match k {
42            EntryKind::Start | EntryKind::Stop => ' ',
43            EntryKind::Ignored => '!',
44            EntryKind::Event => '^'
45        }
46    }
47}
48
49impl EntryKind {
50    /// Create a new [`EntryKind`] from an optional character, defaulting to [`EntryKind::Start`]
51    /// if the character is not recognized.
52    pub fn new<KC>(mark: KC) -> Self
53    where
54        KC: Into<Option<char>>
55    {
56        Self::try_new(mark).unwrap_or(Self::Start)
57    }
58
59    /// Create a new [`EntryKind`] from an optional character.
60    ///
61    /// # Errors
62    ///
63    /// Return [`EntryError::InvalidMarker`] if character not recognized.
64    pub fn try_new<KC>(mark: KC) -> Result<Self, EntryError>
65    where
66        KC: Into<Option<char>>
67    {
68        match mark.into() {
69            Some(' ') | None => Ok(Self::Start),
70            Some('!') => Ok(Self::Ignored),
71            Some('^') => Ok(Self::Event),
72            Some(_) => Err(EntryError::InvalidMarker)
73        }
74    }
75
76    /// Extract the appropriate [`EntryKind`] from supplied entry line.
77    pub fn from_entry_line(line: &str) -> Self {
78        let marker = MARKER_RE.captures(line)
79            .map(|cap| cap.get(1).and_then(|m| m.as_str().chars().next()));
80        Self::new(marker.flatten())
81    }
82
83    /// Is a start marker
84    pub fn is_start(&self) -> bool { *self == Self::Start }
85
86    /// Is an ignored entry marker
87    pub fn is_ignored(&self) -> bool { *self == Self::Ignored }
88
89    /// Is an event marker
90    pub fn is_event(&self) -> bool { *self == Self::Event }
91
92    /// Is a stop marker
93    pub fn is_stop(&self) -> bool { *self == Self::Stop }
94}
95
96#[cfg(test)]
97mod tests {
98    use spectral::prelude::*;
99
100    use super::*;
101
102    #[test]
103    #[rustfmt::skip]
104    fn try_new_successes() {
105        assert_that!(EntryKind::try_new(None)).is_ok().is_equal_to(EntryKind::Start);
106        assert_that!(EntryKind::try_new(' ')).is_ok().is_equal_to(EntryKind::Start);
107        assert_that!(EntryKind::try_new('!')).is_ok().is_equal_to(EntryKind::Ignored);
108        assert_that!(EntryKind::try_new('^')).is_ok().is_equal_to(EntryKind::Event);
109    }
110
111    #[test]
112    fn try_new_fails() {
113        assert_that!(EntryKind::try_new('a')).is_err();
114        assert_that!(EntryKind::try_new('8')).is_err();
115        assert_that!(EntryKind::try_new('*')).is_err();
116    }
117
118    #[test]
119    fn new_never_fails() {
120        assert_that!(EntryKind::new(None)).is_equal_to(EntryKind::Start);
121        assert_that!(EntryKind::new(' ')).is_equal_to(EntryKind::Start);
122        assert_that!(EntryKind::new('!')).is_equal_to(EntryKind::Ignored);
123        assert_that!(EntryKind::new('^')).is_equal_to(EntryKind::Event);
124        assert_that!(EntryKind::new('a')).is_equal_to(EntryKind::Start);
125        assert_that!(EntryKind::new('8')).is_equal_to(EntryKind::Start);
126        assert_that!(EntryKind::new('*')).is_equal_to(EntryKind::Start);
127    }
128
129    #[test]
130    fn char_from_kind() {
131        assert_that!(char::from(EntryKind::Start)).is_equal_to(' ');
132        assert_that!(char::from(EntryKind::Stop)).is_equal_to(' ');
133        assert_that!(char::from(EntryKind::Ignored)).is_equal_to('!');
134        assert_that!(char::from(EntryKind::Event)).is_equal_to('^');
135    }
136
137    #[test]
138    fn from_entry_line_on_start() {
139        #[rustfmt::skip]
140        assert_that!(EntryKind::from_entry_line("2013-06-05 10:00:02 +test @Commented"))
141            .is_equal_to(EntryKind::Start);
142    }
143
144    #[test]
145    fn from_entry_line_on_ignored() {
146        #[rustfmt::skip]
147        assert_that!(EntryKind::from_entry_line("2013-06-05 10:00:02!+test @Ignored"))
148            .is_equal_to(EntryKind::Ignored);
149    }
150
151    #[test]
152    fn from_entry_line_on_pin() {
153        #[rustfmt::skip]
154        assert_that!(EntryKind::from_entry_line("2013-06-05 10:00:02^+test @Event"))
155            .is_equal_to(EntryKind::Event);
156    }
157
158    #[test]
159    fn get_line_entry_kind_on_comment() {
160        #[rustfmt::skip]
161        assert_that!(EntryKind::from_entry_line("#2013-06-05 10:00:02 +test @Commented"))
162            .is_equal_to(EntryKind::Start);
163    }
164}