orgize/elements/
planning.rs

1use memchr::memchr;
2
3use crate::elements::Timestamp;
4
5/// Planning element
6#[cfg_attr(test, derive(PartialEq))]
7#[cfg_attr(feature = "ser", derive(serde::Serialize))]
8#[derive(Debug, Clone)]
9pub struct Planning<'a> {
10    /// Timestamp associated to deadline keyword
11    #[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
12    pub deadline: Option<Timestamp<'a>>,
13    /// Timestamp associated to scheduled keyword
14    #[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
15    pub scheduled: Option<Timestamp<'a>>,
16    /// Timestamp associated to closed keyword
17    #[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
18    pub closed: Option<Timestamp<'a>>,
19}
20
21impl Planning<'_> {
22    #[inline]
23    pub(crate) fn parse(text: &str) -> Option<(&str, Planning)> {
24        let (mut deadline, mut scheduled, mut closed) = (None, None, None);
25        let (mut tail, off) = memchr(b'\n', text.as_bytes())
26            .map(|i| (text[..i].trim(), i + 1))
27            .unwrap_or_else(|| (text.trim(), text.len()));
28
29        while let Some(i) = memchr(b' ', tail.as_bytes()) {
30            let next = &tail[i + 1..].trim_start();
31
32            macro_rules! set_timestamp {
33                ($timestamp:expr) => {{
34                    let (new_tail, timestamp) =
35                        Timestamp::parse_active(next).or(Timestamp::parse_inactive(next))?;
36                    $timestamp = Some(timestamp);
37                    tail = new_tail.trim_start();
38                }};
39            }
40
41            match &tail[..i] {
42                "DEADLINE:" if deadline.is_none() => set_timestamp!(deadline),
43                "SCHEDULED:" if scheduled.is_none() => set_timestamp!(scheduled),
44                "CLOSED:" if closed.is_none() => set_timestamp!(closed),
45                _ => return None,
46            }
47        }
48
49        if deadline.is_none() && scheduled.is_none() && closed.is_none() {
50            None
51        } else {
52            Some((
53                &text[off..],
54                Planning {
55                    deadline,
56                    scheduled,
57                    closed,
58                },
59            ))
60        }
61    }
62
63    pub fn into_owned(self) -> Planning<'static> {
64        Planning {
65            deadline: self.deadline.map(|x| x.into_owned()),
66            scheduled: self.scheduled.map(|x| x.into_owned()),
67            closed: self.closed.map(|x| x.into_owned()),
68        }
69    }
70}
71
72#[test]
73fn prase() {
74    use crate::elements::Datetime;
75
76    assert_eq!(
77        Planning::parse("SCHEDULED: <2019-04-08 Mon>\n"),
78        Some((
79            "",
80            Planning {
81                scheduled: Some(Timestamp::Active {
82                    start: Datetime {
83                        year: 2019,
84                        month: 4,
85                        day: 8,
86                        dayname: "Mon".into(),
87                        hour: None,
88                        minute: None
89                    },
90                    repeater: None,
91                    delay: None
92                }),
93                deadline: None,
94                closed: None,
95            }
96        ))
97    )
98}