Commit Diff


commit - e79ffd7b92c4807a481f6ad3d27605e484f4f58d
commit + 01ba7a35eb3e4796a5d0cdf8a5d72c53d4a7c3e4
blob - 38e4d1b8f5e505c581996508721002c641872c81
blob + e6891cd04ab7707206157794d5d2314001bfaa1a
--- src/lib.rs
+++ src/lib.rs
@@ -38,7 +38,7 @@ use nom::Finish;
 /// let input = "2023-01-05 sample task";
 /// let res = parse_from_str(input)?;
 /// assert_eq!(res, vec![Task::new(Completion::Incomplete, 0, Some(NaiveDate::from_ymd_opt(2023, 1, 5).unwrap()), "sample task")]);
-/// assert_eq!(res.iter().map(Task::tags).collect::<Vec<&Vec<Tag>>>(), vec![&vec![]]);
+/// assert_eq!(res.iter().map(Task::tags).collect::<Vec<Vec<Tag>>>(), vec![vec![]]);
 /// # Ok::<(), TodoError>(())
 /// ```
 ///
blob - f7807f7a2f69ab415d0e9463f3b32ef1bafe4f90
blob + 430004c8b9e8287caac59bd1211e1cdfd9fb36e5
--- src/parser.rs
+++ src/parser.rs
@@ -533,7 +533,7 @@ mod tests {
                 "a perfectly normal task +testing @home due:2023-01-02"
             )
         );
-        assert_eq!(t1.tags(), &tags1);
+        assert_eq!(t1.tags(), tags1);
 
         let (_ ,t2) = task("x 2023-01-02 2023-01-01 a completed task with completion date +testing @home due:2023-01-02 @place +todotxt-parser").unwrap();
         let tags2 = vec![
@@ -547,7 +547,7 @@ mod tests {
             t2,
             Task::new(Completion::Complete(Some(NaiveDate::from_ymd_opt(2023, 01, 02).unwrap())), 0, Some(NaiveDate::from_ymd_opt(2023, 01, 01).unwrap()), "a completed task with completion date +testing @home due:2023-01-02 @place +todotxt-parser")
         );
-        assert_eq!(t2.tags(), &tags2);
+        assert_eq!(t2.tags(), tags2);
     }
 
     #[test]
@@ -582,8 +582,8 @@ mod tests {
                 Task::new(Completion::Complete(Some(NaiveDate::from_ymd_opt(2023, 01, 02).unwrap())), 0, 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")
             ]
         );
-        assert_eq!(ts[0].tags(), &tags1);
-        assert_eq!(ts[1].tags(), &tags2);
+        assert_eq!(ts[0].tags(), tags1);
+        assert_eq!(ts[1].tags(), tags2);
     }
 
     #[test]
blob - d0dd3c3b174c908a313e24cb9f73e95a9a0614d4
blob + 38241164113bfce4b50fd85ba3c75ae01d2f058f
--- src/types.rs
+++ src/types.rs
@@ -8,7 +8,7 @@ use crate::err::TodoError;
 use crate::parser;
 use chrono::naive::NaiveDate;
 use nom::Finish;
-use std::num::NonZeroU8;
+use std::{borrow::Cow, num::NonZeroU8};
 
 /// A Tag in a Task.
 #[non_exhaustive]
@@ -172,15 +172,7 @@ pub struct Task<'a> {
     /// Date of creation.
     pub creation_date: Option<NaiveDate>,
     /// Description of the [`Task`].
-    // `description` can't be public as it is closely tied to `tags`. Changing one means changing
-    // the other. Access and modification should be done through separate methods.
-    description: &'a str,
-    /// Associated [`Tag`]s.
-    ///
-    /// For a list of supported tags, see [`Tag`].
-    // `tags` can't be public as it is closely tied to `description`. Changing one means changing
-    // the other. Access and modification should be done through separate methods.
-    tags: Vec<Tag<'a>>,
+    description: Cow<'a, str>,
 }
 
 impl<'a> Task<'a> {
@@ -195,7 +187,7 @@ impl<'a> Task<'a> {
     /// # use chrono::NaiveDate;
     /// let raw_task = "x (A) 2016-05-20 2016-04-30 measure space for +chapelShelving @chapel due:2016-05-30";
     /// let task = Task::try_from(raw_task)?;
-    /// assert_eq!(task.tags(), &vec![
+    /// assert_eq!(task.tags(), vec![
     ///     Tag::Project("chapelShelving"),
     ///     Tag::Context("chapel"),
     ///     Tag::Due(NaiveDate::from_ymd_opt(2016, 5, 30).unwrap())
@@ -209,72 +201,58 @@ impl<'a> Task<'a> {
         creation_date: Option<NaiveDate>,
         description: &'a str,
     ) -> Self {
-        let tags = parser::description_tags(description)
-            .map(|(_, res)| res)
-            .unwrap_or_default();
-
         Self {
             completion,
             priority,
             creation_date,
-            description,
-            tags,
+            description: Cow::from(description),
         }
     }
 
     #[must_use]
+    pub const fn has_changed(&self) -> bool {
+        matches!(self.description, Cow::Owned(_))
+    }
+
+    #[must_use]
     pub const fn completion(&self) -> &Completion {
         &self.completion
     }
 
-    pub fn set_completion(&mut self, completion: Completion) {
-        self.completion = completion;
-    }
-
     #[must_use]
     pub const fn priority(&self) -> u8 {
         self.priority
     }
 
-    pub fn set_priority(&mut self, priority: u8) {
-        self.priority = priority;
-    }
-
     #[must_use]
     pub const fn creation_date(&self) -> Option<NaiveDate> {
         self.creation_date
     }
 
-    pub fn set_creation_date(&mut self, creation_date: Option<NaiveDate>) {
-        self.creation_date = creation_date;
+    #[must_use]
+    pub fn description(&'a self) -> &'a str {
+        self.description.as_ref()
     }
 
-    #[must_use]
-    pub const fn description(&self) -> &'a str {
-        self.description
+    pub fn set_description(&mut self, description: String) {
+        self.description = Cow::from(description);
     }
 
-    pub fn set_description(&mut self, description: &'a str) {
-        let tags = parser::description_tags(description)
+    #[must_use]
+    pub fn tags(&self) -> Vec<Tag> {
+        parser::description_tags(self.description.as_ref())
             .map(|(_, res)| res)
-            .unwrap_or_default();
-        self.description = description;
-        self.tags = tags;
+            .unwrap_or_default()
     }
 
-    #[must_use]
-    pub const fn tags(&self) -> &Vec<Tag> {
-        &self.tags
-    }
-
     /// Return the projects found in the description of the task.
     #[must_use]
-    pub fn projects(&self) -> Vec<&'a str> {
-        self.tags
-            .iter()
+    pub fn projects(&'a self) -> Vec<&'a str> {
+        self.tags()
+            .into_iter()
             .filter_map(|t| {
                 if let Tag::Project(p) = t {
-                    Some(*p)
+                    Some(p)
                 } else {
                     None
                 }
@@ -284,12 +262,12 @@ impl<'a> Task<'a> {
 
     /// Return the contexts found in the description of the task.
     #[must_use]
-    pub fn contexts(&self) -> Vec<&'a str> {
-        self.tags
-            .iter()
+    pub fn contexts(&'a self) -> Vec<&'a str> {
+        self.tags()
+            .into_iter()
             .filter_map(|t| {
                 if let Tag::Context(c) = t {
-                    Some(*c)
+                    Some(c)
                 } else {
                     None
                 }
@@ -299,17 +277,17 @@ impl<'a> Task<'a> {
 
     /// Return the due date of the task, if there is one.
     #[must_use]
-    pub fn due(&self) -> Option<&NaiveDate> {
-        self.tags
-            .iter()
+    pub fn due(&self) -> Option<NaiveDate> {
+        self.tags()
+            .into_iter()
             .find_map(|t| if let Tag::Due(d) = t { Some(d) } else { None })
     }
 
     /// Return the recurring rule of the task, if there is one.
     #[must_use]
-    pub fn rec(&self) -> Option<&Recurring> {
-        self.tags
-            .iter()
+    pub fn rec(&self) -> Option<Recurring> {
+        self.tags()
+            .into_iter()
             .find_map(|t| if let Tag::Rec(r) = t { Some(r) } else { None })
     }
 }
@@ -360,17 +338,11 @@ impl<'a>
                 }
             };
 
-        let tags = parser::description_tags(description)
-            .finish()
-            .map_err(|_| TodoError::ParsingError("description tags"))?
-            .1;
-
         Ok(Task {
             completion: (completed, completion_date).try_into()?,
             priority,
             creation_date,
-            description,
-            tags,
+            description: description.into(),
         })
     }
 }
@@ -430,10 +402,9 @@ mod test {
             completion: Completion::Incomplete,
             priority: 0,
             creation_date: None,
-            description: "sample task +project rec:1y",
-            tags: vec![Tag::Project("project"), Tag::Rec(rec.clone())],
+            description: Cow::from("sample task +project rec:1y"),
         };
 
-        assert_eq!(task.rec(), Some(&rec));
+        assert_eq!(task.rec(), Some(rec));
     }
 }
blob - 33bed8d1a6b05432b25add2ec1484285a2d4aa09
blob + 95c393ca1113f99f48105505fa56230299a75a51
--- tests/test.rs
+++ tests/test.rs
@@ -12,12 +12,11 @@ fn test_parse_from_str_oneline() {
     let input =
         "2023-01-03 write tests for todotxt-parser +todotxt-parser @workstation due:2023-01-04";
     let ts = parse_from_str(&input).unwrap();
-    let tags1 = vec![
+    let tags = vec![vec![
         Tag::Project("todotxt-parser"),
         Tag::Context("workstation"),
         Tag::Due(NaiveDate::from_ymd_opt(2023, 1, 4).unwrap()),
-    ];
-    let tags = vec![&tags1];
+    ]];
 
     assert_eq!(
         ts,
@@ -37,17 +36,18 @@ fn test_parse_from_str() {
         "2023-01-03 write tests for todotxt-parser +todotxt-parser @workstation due:2023-01-04\n\
             x 2023-01-02 grocery shopping +groceries @supermarket due:2023-01-03";
     let ts = parse_from_str(input).unwrap();
-    let tags1 = vec![
-        Tag::Project("todotxt-parser"),
-        Tag::Context("workstation"),
-        Tag::Due(NaiveDate::from_ymd_opt(2023, 1, 4).unwrap()),
+    let tags = vec![
+        vec![
+            Tag::Project("todotxt-parser"),
+            Tag::Context("workstation"),
+            Tag::Due(NaiveDate::from_ymd_opt(2023, 1, 4).unwrap()),
+        ],
+        vec![
+            Tag::Project("groceries"),
+            Tag::Context("supermarket"),
+            Tag::Due(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()),
+        ],
     ];
-    let tags2 = vec![
-        Tag::Project("groceries"),
-        Tag::Context("supermarket"),
-        Tag::Due(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()),
-    ];
-    let tags = vec![&tags1, &tags2];
 
     assert_eq!(
         ts,
blob - 69a61ac9e58924aa1b196262ccfc2ae3fdb240cd
blob + 16a05dcca83a4a55bc6d9ee3e5d3ed449c0bd86d
--- tests/types.rs
+++ tests/types.rs
@@ -27,5 +27,5 @@ fn test_task_try_from_str() {
             "measure space for +chapelShelving @chapel due:2016-05-30"
         )
     );
-    assert_eq!(res.tags(), &tags);
+    assert_eq!(res.tags(), tags);
 }