#[cfg(doc)]
use crate::entry::Entry;
use crate::entry::{EntryError, MARKER_RE};
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum EntryKind {
Start,
Stop,
Ignored,
Event
}
impl From<EntryKind> for char {
fn from(k: EntryKind) -> char {
match k {
EntryKind::Start | EntryKind::Stop => ' ',
EntryKind::Ignored => '!',
EntryKind::Event => '^'
}
}
}
impl EntryKind {
pub fn new<KC>(mark: KC) -> Self
where
KC: Into<Option<char>>
{
Self::try_new(mark).unwrap_or(Self::Start)
}
pub fn try_new<KC>(mark: KC) -> Result<Self, EntryError>
where
KC: Into<Option<char>>
{
match mark.into() {
Some(' ') | None => Ok(Self::Start),
Some('!') => Ok(Self::Ignored),
Some('^') => Ok(Self::Event),
Some(_) => Err(EntryError::InvalidMarker)
}
}
pub fn from_entry_line(line: &str) -> Self {
let marker = MARKER_RE.captures(line)
.map(|cap| cap.get(1).and_then(|m| m.as_str().chars().next()));
Self::new(marker.flatten())
}
pub fn is_start(&self) -> bool { *self == Self::Start }
pub fn is_ignored(&self) -> bool { *self == Self::Ignored }
pub fn is_event(&self) -> bool { *self == Self::Event }
pub fn is_stop(&self) -> bool { *self == Self::Stop }
}
#[cfg(test)]
mod tests {
use assert2::{assert, let_assert};
use rstest::rstest;
use super::*;
#[rstest]
#[case(None, EntryKind::Start, "none")]
#[case(Some(' '), EntryKind::Start, "space")]
#[case(Some('!'), EntryKind::Ignored, "bang")]
#[case(Some('^'), EntryKind::Event, "caret")]
fn try_new_success_with_option(
#[case]input: Option<char>,
#[case]expected: EntryKind,
#[case]msg: &str
) {
let_assert!(Ok(actual) = EntryKind::try_new(input), "{msg}");
assert!(actual == expected);
}
#[rstest]
#[case(' ', EntryKind::Start, "space")]
#[case('!', EntryKind::Ignored, "bang")]
#[case('^', EntryKind::Event, "caret")]
fn try_new_success_with_char(
#[case]input: char,
#[case]expected: EntryKind,
#[case]msg: &str
) {
let_assert!(Ok(actual) = EntryKind::try_new(input), "{msg}");
assert!(actual == expected);
}
#[rstest]
#[case('a')]
#[case('8')]
#[case('*')]
fn try_new_fails(#[case]input: char) {
assert!(EntryKind::try_new(input).is_err());
}
#[test]
fn new_succeeds_on_none() {
assert!(EntryKind::new(None) == EntryKind::Start);
}
#[rstest]
#[case(' ', EntryKind::Start, "space")]
#[case('!', EntryKind::Ignored, "bang")]
#[case('^', EntryKind::Event, "caret")]
#[case('a', EntryKind::Start, "invalid letter")]
#[case('8', EntryKind::Start, "invalid number")]
#[case('*', EntryKind::Start, "invalid special")]
fn new_never_fails(#[case]input: char, #[case]expected: EntryKind, #[case]msg: &str) {
assert!(EntryKind::new(input) == expected, "{msg}");
}
#[rstest]
#[case(EntryKind::Start, ' ', "start")]
#[case(EntryKind::Stop, ' ', "stop")]
#[case(EntryKind::Ignored, '!', "ignored")]
#[case(EntryKind::Event, '^', "event")]
fn char_from_kind(#[case]kind: EntryKind, #[case]out: char, #[case]msg: &str) {
assert!(char::from(kind) == out, "{msg}");
}
#[rstest]
#[case("2013-06-05 10:00:02 +test @Commented", EntryKind::Start, "start line")]
#[case("2013-06-05 10:00:02!+test @Ignored", EntryKind::Ignored, "ignored line")]
#[case("2013-06-05 10:00:02^+test @Event", EntryKind::Event, "pinned line")]
#[case("#2013-06-05 10:00:02 +test @Commented", EntryKind::Start, "commented line")]
fn from_entry_line(#[case]input: &str, #[case]expected: EntryKind, #[case]msg: &str) {
assert!(EntryKind::from_entry_line(input) == expected, "{msg}");
}
}