Commit Diff


commit - c6bbc32edce650be04dc40fbc37cbd56dea19819
commit + a6078516315466372617a4c3b72a6b88d04ad76b
blob - af4b5dbd2ab934d82ac95ac79ff2cb7de160e0bf
blob + 619b035850901ba580d8d12a32b873bdb8c815ba
--- src/err.rs
+++ src/err.rs
@@ -13,6 +13,9 @@ pub enum TodoError {
     /// The input has an incorrect format.
     #[error("Incorrect format: {0}")]
     IncorrectFormat(&'static str),
+    /// Illegal arguments have been passed.
+    #[error("Illegal arguments: {0}")]
+    IllegalArguments(&'static str),
     /// An error occurred while parsing the input.
     #[error("Error while parsing: {0}")]
     ParsingError(&'static str),
blob - 90d7b67f27f240c2226b38c47362f18ff5c422c6
blob + 93be29963141c72b6fc164cf97828d4c2448738c
--- src/lib.rs
+++ src/lib.rs
@@ -24,7 +24,7 @@ mod parser;
 mod types;
 
 pub use err::TodoError;
-pub use types::{Recurring, Tag, Task, TimeAmount, TimeUnit};
+pub use types::{Completion, Recurring, Tag, Task, TimeAmount, TimeUnit};
 
 use nom::Finish;
 
@@ -33,11 +33,17 @@ use nom::Finish;
 /// This is the main entry point of the library.
 ///
 /// ```rust
-/// # use todotxt_parser::{parse_from_str, Task, TodoError};
+/// # use todotxt_parser::{Completion, Task, TodoError, parse_from_str};
 /// # use chrono::NaiveDate;
 /// let input = "2023-01-05 sample task";
 /// let res = parse_from_str(input)?;
-/// assert_eq!(res, vec![Task::new(false, 0, None, Some(NaiveDate::from_ymd_opt(2023, 1, 5).unwrap()), "sample task", vec![])]);
+/// assert_eq!(res, vec![Task {
+///         completion: Completion::Incomplete,
+///         priority: 0,
+///         creation_date: Some(NaiveDate::from_ymd_opt(2023, 1, 5).unwrap()),
+///         description: "sample task",
+///         tags: vec![]
+///     }]);
 /// # Ok::<(), TodoError>(())
 /// ```
 ///
blob - 486f07c6fa14e952d651a804ec4d8c6ab195da37
blob + ff10e0718af6f6f74f07409dfa6045cc7afcbb4d
--- src/parser.rs
+++ src/parser.rs
@@ -170,7 +170,7 @@ fn time_amount(i: &str) -> IResult<&str, TimeAmount> {
             verify(map_res(digit1, str::parse::<usize>), |d: &usize| *d > 0),
             time_unit,
         )),
-        |(amount, unit)| TimeAmount::new(amount, unit),
+        |(amount, unit)| TimeAmount { amount, unit },
     )(i)
 }
 
@@ -180,7 +180,7 @@ pub fn recurring(i: &str) -> IResult<&str, Recurring> 
             map(opt(value(true, tag("+"))), |v| v.unwrap_or(false)),
             time_amount,
         )),
-        |(strict, amount)| Recurring::new(strict, amount),
+        |(strict, amount)| Recurring { strict, amount },
     )(i)
 }
 
@@ -240,7 +240,7 @@ pub fn tasks(i: &str) -> IResult<&str, Vec<Task>> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::types::Tag;
+    use crate::types::{Completion, Recurring, Tag, Task, TimeAmount, TimeUnit};
     use nom::error::{Error, ErrorKind};
 
     #[test]
@@ -497,10 +497,13 @@ mod tests {
                 vec![
                     Tag::KeyValue(("key", "value")),
                     Tag::Context("context"),
-                    Tag::Rec(Recurring::new(
-                        false,
-                        TimeAmount::new(132049, TimeUnit::Day)
-                    )),
+                    Tag::Rec(Recurring {
+                        strict: false,
+                        amount: TimeAmount {
+                            amount: 132049,
+                            unit: TimeUnit::Day
+                        }
+                    }),
                     Tag::Project("project"),
                 ],
             ))
@@ -518,18 +521,17 @@ mod tests {
             task("(A) 2023-01-01 a perfectly normal task +testing @home due:2023-01-02"),
             Ok((
                 "",
-                Task::new(
-                    false,
-                    1,
-                    None,
-                    Some(NaiveDate::from_ymd_opt(2023, 01, 01).unwrap()),
-                    "a perfectly normal task +testing @home due:2023-01-02",
-                    vec![
+                Task {
+                    completion: Completion::Incomplete,
+                    priority: 1,
+                    creation_date: Some(NaiveDate::from_ymd_opt(2023, 01, 01).unwrap()),
+                    description: "a perfectly normal task +testing @home due:2023-01-02",
+                    tags: vec![
                         Tag::Project("testing"),
                         Tag::Context("home"),
                         Tag::Due(NaiveDate::from_ymd_opt(2023, 01, 02).unwrap())
                     ],
-                )
+                }
             ))
         );
 
@@ -537,20 +539,19 @@ mod tests {
             task("x 2023-01-02 2023-01-01 a completed task with completion date +testing @home due:2023-01-02 @place +todotxt-parser"),
             Ok((
                 "",
-                Task::new(
-                    true,
-                    0,
-                    Some(NaiveDate::from_ymd_opt(2023, 01, 02).unwrap()),
-                    Some(NaiveDate::from_ymd_opt(2023, 01, 01).unwrap()),
-                    "a completed task with completion date +testing @home due:2023-01-02 @place +todotxt-parser",
-                    vec![
+                Task {
+                    completion: Completion::Complete(Some(NaiveDate::from_ymd_opt(2023, 01, 02).unwrap())),
+                    priority: 0,
+                    creation_date: Some(NaiveDate::from_ymd_opt(2023, 01, 01).unwrap()),
+                    description: "a completed task with completion date +testing @home due:2023-01-02 @place +todotxt-parser",
+                    tags: vec![
                         Tag::Project("testing"),
                         Tag::Context("home"),
                         Tag::Due(NaiveDate::from_ymd_opt(2023,01,02).unwrap()),
                         Tag::Context("place"),
                         Tag::Project("todotxt-parser"),
                     ],
-                ),
+                },
             ))
         );
     }
@@ -565,33 +566,34 @@ mod tests {
             Ok((
                 "",
                 vec![
-                    Task::new(
-                        false,
-                        1,
-                        None,
-                        Some(NaiveDate::from_ymd_opt(2023, 01, 01).unwrap()),
-                        "a perfectly normal task +testing @home due:2023-01-02",
-                        vec![
+                    Task {
+                        completion: Completion::Incomplete,
+                        priority: 1,
+                        creation_date: Some(NaiveDate::from_ymd_opt(2023, 01, 01).unwrap()),
+                        description: "a perfectly normal task +testing @home due:2023-01-02",
+                        tags: vec![
                             Tag::Project("testing"),
                             Tag::Context("home"),
                             Tag::Due(NaiveDate::from_ymd_opt(2023,01,02).unwrap()),
                         ],
-                    ),
-                    Task::new(
-                        true,
-                        0,
-                        Some(NaiveDate::from_ymd_opt(2023, 01, 02).unwrap()),
-                        Some(NaiveDate::from_ymd_opt(2023, 01, 01).unwrap()),
-            "a completed task with completion date +testing @home due:2023-01-02 @place +todotxt-parser and recurring every rec:+4m 4 months strictly",
-                        vec![
+                    },
+                    Task {
+                        completion: Completion::Complete(Some(NaiveDate::from_ymd_opt(2023, 01, 02).unwrap())),
+                        priority: 0,
+                        creation_date: Some(NaiveDate::from_ymd_opt(2023, 01, 01).unwrap()),
+                        description: "a completed task with completion date +testing @home due:2023-01-02 @place +todotxt-parser and recurring every rec:+4m 4 months strictly",
+                        tags: vec![
                             Tag::Project("testing"),
                             Tag::Context("home"),
                             Tag::Due(NaiveDate::from_ymd_opt(2023,01,02).unwrap()),
                             Tag::Context("place"),
                             Tag::Project("todotxt-parser"),
-                            Tag::Rec(Recurring::new(true, TimeAmount::new(4, TimeUnit::Month))),
+                            Tag::Rec(Recurring {
+                                strict: true,
+                                amount: TimeAmount {
+                                amount:4, unit :TimeUnit::Month}})
                         ],
-                    ),
+                    },
                 ],
             ))
         );
@@ -603,14 +605,26 @@ mod tests {
             recurring("+5m"),
             Ok((
                 "",
-                Recurring::new(true, TimeAmount::new(5, TimeUnit::Month))
+                Recurring {
+                    strict: true,
+                    amount: TimeAmount {
+                        amount: 5,
+                        unit: TimeUnit::Month
+                    }
+                }
             ))
         );
         assert_eq!(
             recurring("64y"),
             Ok((
                 "",
-                Recurring::new(false, TimeAmount::new(64, TimeUnit::Year))
+                Recurring {
+                    strict: false,
+                    amount: TimeAmount {
+                        amount: 64,
+                        unit: TimeUnit::Year
+                    }
+                }
             ))
         );
     }
blob - ee842dc9b02f370b4e5c8e8126c770fbdd326916
blob + 3ad4f7ef9998a63833bd53630e40304a3edcd283
--- src/types.rs
+++ src/types.rs
@@ -85,19 +85,11 @@ pub struct Recurring {
     /// task.
     /// If `false`, the new task will be created an amount of time after the *completion* of the
     /// task.
-    strict: bool,
+    pub strict: bool,
     /// What interval the task should be recurring in.
-    amount: TimeAmount,
+    pub amount: TimeAmount,
 }
 
-impl Recurring {
-    /// Create a new [`Recurring`] instance.
-    #[must_use]
-    pub const fn new(strict: bool, amount: TimeAmount) -> Self {
-        Self { strict, amount }
-    }
-}
-
 /// A unit of time applicable for the [`Recurring`] tag.
 #[derive(Debug, PartialEq, Eq)]
 pub enum TimeUnit {
@@ -129,96 +121,65 @@ impl TryFrom<char> for TimeUnit {
 #[derive(Debug, PartialEq, Eq)]
 pub struct TimeAmount {
     /// The actual amount.
-    amount: usize,
+    pub amount: usize,
     /// The unit.
-    unit: TimeUnit,
+    pub unit: TimeUnit,
 }
 
-impl TimeAmount {
-    /// Create a new [`TimeAmount`] instance.
-    #[must_use]
-    pub const fn new(amount: usize, unit: TimeUnit) -> Self {
-        Self { amount, unit }
+/// The completion state of a [`Task`].
+///
+/// A task can either be [`Incomplete`] or [`Complete`]. Should the task be complete, there can
+/// optionally be a completion date associated with it, marking the date of completion.
+///
+/// [`Incomplete`]: Self::Incomplete
+/// [`Complete`]: Self::Complete
+#[derive(Debug, PartialEq, Eq)]
+pub enum Completion {
+    Incomplete,
+    Complete(Option<NaiveDate>),
+}
+
+impl TryFrom<(bool, Option<NaiveDate>)> for Completion {
+    type Error = TodoError;
+
+    fn try_from(
+        (completed, completion_date): (bool, Option<NaiveDate>),
+    ) -> Result<Self, Self::Error> {
+        if !completed && completion_date.is_some() {
+            return Err(TodoError::IllegalArguments(
+                "Received completion date, but task is incomplete",
+            ));
+        }
+
+        Ok(if completed {
+            Self::Complete(completion_date)
+        } else {
+            Self::Incomplete
+        })
     }
 }
 
 /// A single todo.txt task.
-#[derive(Debug, Default, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq)]
 pub struct Task<'a> {
-    /// Whether the task has been completed or not.
-    completed: bool,
-    /// What priority the task is associated with.
+    /// Whether the [`Task`] has been completed or not.
+    pub completion: Completion,
+    /// What priority the [`Task`] is associated with.
     ///
-    /// A priority of 0 indicates no priority. Possible priority values are 1-26, corresponding to
-    /// letters A-Z.
-    priority: u8,
-    /// Date of completion.
-    completion_date: Option<NaiveDate>,
+    /// A priority of 0 indicates no priority. Possible priority values are in range `1..=26`,
+    /// corresponding to letters in range `A..=Z`.
+    pub priority: u8,
     /// Date of creation.
-    creation_date: Option<NaiveDate>,
-    /// Description of the task.
-    description: &'a str,
-    /// Tags associated with the task.
+    pub creation_date: Option<NaiveDate>,
+    /// Description of the [`Task`].
+    pub description: &'a str,
+    /// Associated [`Tag`]s.
     ///
     /// For a list of supported tags, see [`Tag`].
-    tags: Vec<Tag<'a>>,
+    pub tags: Vec<Tag<'a>>,
 }
 
 impl<'a> Task<'a> {
-    /// Create a new [`Task`].
-    #[must_use]
-    pub const fn new(
-        completed: bool,
-        priority: u8,
-        completion_date: Option<NaiveDate>,
-        creation_date: Option<NaiveDate>,
-        description: &'a str,
-        tags: Vec<Tag<'a>>,
-    ) -> Self {
-        Self {
-            completed,
-            priority,
-            completion_date,
-            creation_date,
-            description,
-            tags,
-        }
-    }
-
-    /// Return whether the task has been completed yet or not.
-    #[must_use]
-    pub const fn completed(&self) -> bool {
-        self.completed
-    }
-
-    /// Return the priority of a task. A priority of `0` means no priority has been set.
-    #[must_use]
-    pub const fn priority(&self) -> u8 {
-        self.priority
-    }
-
-    /// Return the completion date of the task, if there is one.
-    ///
-    /// This function should always return [`None`] if the task has not been completed yet.
-    #[must_use]
-    pub const fn completion_date(&self) -> Option<NaiveDate> {
-        debug_assert!(!self.completed && self.completion_date.is_none());
-
-        self.completion_date
-    }
-
-    /// Return the creation date of the task, if there is one.
-    #[must_use]
-    pub const fn creation_date(&self) -> Option<NaiveDate> {
-        self.creation_date
-    }
-
-    /// Return the description of the task.
-    #[must_use]
-    pub const fn description(&self) -> &'a str {
-        self.description
-    }
-
     /// Return the projects found in the description of the task.
     #[must_use]
     pub fn projects(&self) -> Vec<&'a str> {
@@ -310,9 +271,8 @@ impl<'a>
             .1;
 
         Ok(Task {
-            completed,
+            completion: (completed, completion_date).try_into()?,
             priority,
-            completion_date,
             creation_date,
             description,
             tags,
@@ -348,10 +308,13 @@ mod test {
 
         assert_eq!(
             Tag::new_key_value("rec", "+420y"),
-            Ok(Tag::Rec(Recurring::new(
-                true,
-                TimeAmount::new(420, TimeUnit::Year)
-            ))),
+            Ok(Tag::Rec(Recurring {
+                strict: true,
+                amount: TimeAmount {
+                    amount: 420,
+                    unit: TimeUnit::Year
+                }
+            })),
         );
         assert!(matches!(
             Tag::new_key_value("rec", "0d"),
blob - 56454eb4cdb425ac8c11862e0ba2d709eaa78570
blob + ee256ea0a92d53e6d320c4cda40e76b284bd50cf
--- tests/test.rs
+++ tests/test.rs
@@ -5,7 +5,7 @@
  */
 
 use chrono::NaiveDate;
-use todotxt_parser::{parse_from_str, Tag, Task};
+use todotxt_parser::{parse_from_str, Completion, Tag, Task};
 
 #[test]
 fn test_parse_from_str_oneline() {
@@ -15,18 +15,18 @@ fn test_parse_from_str_oneline() {
 
     assert_eq!(
         ts,
-        vec![Task::new(
-            false,
-            0,
-            None,
-            Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()),
-            "write tests for todotxt-parser +todotxt-parser @workstation due:2023-01-04",
-            vec![
+        vec![Task {
+            completion: Completion::Incomplete,
+            priority: 0,
+            creation_date: Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()),
+            description:
+                "write tests for todotxt-parser +todotxt-parser @workstation due:2023-01-04",
+            tags: vec![
                 Tag::Project("todotxt-parser"),
                 Tag::Context("workstation"),
                 Tag::Due(NaiveDate::from_ymd_opt(2023, 1, 4).unwrap()),
             ],
-        )]
+        }]
     );
 }
 
@@ -40,30 +40,31 @@ fn test_parse_from_str() {
     assert_eq!(
         ts,
         vec![
-            Task::new(
-                false,
-                0,
-                None,
-                Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()),
-                "write tests for todotxt-parser +todotxt-parser @workstation due:2023-01-04",
-                vec![
+            Task {
+                completion: Completion::Incomplete,
+                priority: 0,
+                creation_date: Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()),
+                description:
+                    "write tests for todotxt-parser +todotxt-parser @workstation due:2023-01-04",
+                tags: vec![
                     Tag::Project("todotxt-parser"),
                     Tag::Context("workstation"),
                     Tag::Due(NaiveDate::from_ymd_opt(2023, 1, 4).unwrap()),
                 ],
-            ),
-            Task::new(
-                true,
-                0,
-                Some(NaiveDate::from_ymd_opt(2023, 1, 2).unwrap()),
-                None,
-                "grocery shopping +groceries @supermarket due:2023-01-03",
-                vec![
+            },
+            Task {
+                completion: Completion::Complete(Some(
+                    NaiveDate::from_ymd_opt(2023, 1, 2).unwrap()
+                )),
+                priority: 0,
+                creation_date: None,
+                description: "grocery shopping +groceries @supermarket due:2023-01-03",
+                tags: vec![
                     Tag::Project("groceries"),
                     Tag::Context("supermarket"),
                     Tag::Due(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap())
                 ],
-            ),
+            },
         ]
     );
 }
blob - bb99f5509528e4e497358f9312df7a5e236d0c59
blob + 4c7155c585e67c7ae402985b0a31e3cb069b1e45
--- tests/types.rs
+++ tests/types.rs
@@ -5,7 +5,7 @@
  */
 
 use chrono::NaiveDate;
-use todotxt_parser::{Tag, Task};
+use todotxt_parser::{Completion, Tag, Task};
 
 const TASK_STR: &str =
     "x (A) 2016-05-20 2016-04-30 measure space for +chapelShelving @chapel due:2016-05-30";
@@ -15,17 +15,16 @@ fn test_task_try_from_str() {
     let res = Task::try_from(TASK_STR).unwrap();
     assert_eq!(
         res,
-        Task::new(
-            true,
-            1,
-            Some(NaiveDate::from_ymd_opt(2016, 5, 20).unwrap()),
-            Some(NaiveDate::from_ymd_opt(2016, 4, 30).unwrap()),
-            "measure space for +chapelShelving @chapel due:2016-05-30",
-            vec![
+        Task {
+            completion: Completion::Complete(Some(NaiveDate::from_ymd_opt(2016, 5, 20).unwrap())),
+            priority: 1,
+            creation_date: Some(NaiveDate::from_ymd_opt(2016, 4, 30).unwrap()),
+            description: "measure space for +chapelShelving @chapel due:2016-05-30",
+            tags: vec![
                 Tag::Project("chapelShelving"),
                 Tag::Context("chapel"),
                 Tag::Due(NaiveDate::from_ymd_opt(2016, 5, 30).unwrap())
             ]
-        )
+        }
     );
 }