commit - a6078516315466372617a4c3b72a6b88d04ad76b
commit + e79ffd7b92c4807a481f6ad3d27605e484f4f58d
blob - 93be29963141c72b6fc164cf97828d4c2448738c
blob + 38e4d1b8f5e505c581996508721002c641872c81
--- src/lib.rs
+++ src/lib.rs
/// This is the main entry point of the library.
///
/// ```rust
-/// # use todotxt_parser::{Completion, Task, TodoError, parse_from_str};
+/// # use todotxt_parser::{Completion, Tag, 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 {
-/// completion: Completion::Incomplete,
-/// priority: 0,
-/// creation_date: Some(NaiveDate::from_ymd_opt(2023, 1, 5).unwrap()),
-/// description: "sample task",
-/// tags: vec![]
-/// }]);
+/// 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![]]);
/// # Ok::<(), TodoError>(())
/// ```
///
blob - ff10e0718af6f6f74f07409dfa6045cc7afcbb4d
blob + f7807f7a2f69ab415d0e9463f3b32ef1bafe4f90
--- src/parser.rs
+++ src/parser.rs
#[test]
fn test_task() {
+ let (_, t1) =
+ task("(A) 2023-01-01 a perfectly normal task +testing @home due:2023-01-02").unwrap();
+ let tags1 = vec![
+ Tag::Project("testing"),
+ Tag::Context("home"),
+ Tag::Due(NaiveDate::from_ymd_opt(2023, 01, 02).unwrap()),
+ ];
assert_eq!(
- task("(A) 2023-01-01 a perfectly normal task +testing @home due:2023-01-02"),
- Ok((
- "",
- 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())
- ],
- }
- ))
+ t1,
+ Task::new(
+ Completion::Incomplete,
+ 1,
+ Some(NaiveDate::from_ymd_opt(2023, 01, 01).unwrap()),
+ "a perfectly normal task +testing @home due:2023-01-02"
+ )
);
+ 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![
+ Tag::Project("testing"),
+ Tag::Context("home"),
+ Tag::Due(NaiveDate::from_ymd_opt(2023, 01, 02).unwrap()),
+ Tag::Context("place"),
+ Tag::Project("todotxt-parser"),
+ ];
assert_eq!(
- 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 {
- 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"),
- ],
- },
- ))
+ 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);
}
#[test]
fn test_tasks() {
- assert_eq!(
- tasks(
+ let (_, ts) = tasks(
"(A) 2023-01-01 a perfectly normal task +testing @home due:2023-01-02\n\
x 2023-01-02 2023-01-01 a completed task with completion date +testing @home due:2023-01-02 @place +todotxt-parser and recurring every rec:+4m 4 months strictly"
- ),
- Ok((
- "",
- 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 {
- 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 {
- strict: true,
- amount: TimeAmount {
- amount:4, unit :TimeUnit::Month}})
- ],
- },
- ],
- ))
+ ).unwrap();
+ let tags1 = vec![
+ Tag::Project("testing"),
+ Tag::Context("home"),
+ Tag::Due(NaiveDate::from_ymd_opt(2023, 01, 02).unwrap()),
+ ];
+ let tags2 = 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 {
+ strict: true,
+ amount: TimeAmount {
+ amount: 4,
+ unit: TimeUnit::Month,
+ },
+ }),
+ ];
+ assert_eq!(
+ ts,
+ vec![
+ Task::new(Completion::Incomplete, 1, Some(NaiveDate::from_ymd_opt(2023, 01, 01).unwrap()), "a perfectly normal task +testing @home due:2023-01-02"),
+ 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);
}
#[test]
blob - 3ad4f7ef9998a63833bd53630e40304a3edcd283
blob + d0dd3c3b174c908a313e24cb9f73e95a9a0614d4
--- src/types.rs
+++ src/types.rs
///
/// This struct describes how recurring should be handled, and can be used to create another task
/// when it gets completed, according to its rules.
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Recurring {
/// Whether recurring should be strict or not.
///
}
/// A unit of time applicable for the [`Recurring`] tag.
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TimeUnit {
/// A day.
Day,
}
/// An amount of time, specified with a number and a unit.
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TimeAmount {
/// The actual amount.
pub amount: usize,
/// Date of creation.
pub creation_date: Option<NaiveDate>,
/// Description of the [`Task`].
- pub description: &'a str,
+ // `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`].
- pub tags: Vec<Tag<'a>>,
+ // `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>>,
}
impl<'a> Task<'a> {
+ /// Create a new task.
+ ///
+ /// The tags of the task are parsed from the description. As the tags and the description are
+ /// closely tied together, one changing if the other does, mutable access is only granted to
+ /// the description, which, when changed, changes the tags, too.
+ ///
+ /// ```rust
+ /// # use todotxt_parser::{Completion, Tag, Task, TodoError};
+ /// # 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![
+ /// Tag::Project("chapelShelving"),
+ /// Tag::Context("chapel"),
+ /// Tag::Due(NaiveDate::from_ymd_opt(2016, 5, 30).unwrap())
+ /// ]);
+ /// # Ok::<(), TodoError>(())
+ /// ```
+ #[must_use]
+ pub fn new(
+ completion: Completion,
+ priority: u8,
+ 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,
+ }
+ }
+
+ #[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 const fn description(&self) -> &'a str {
+ self.description
+ }
+
+ pub fn set_description(&mut self, description: &'a str) {
+ let tags = parser::description_tags(description)
+ .map(|(_, res)| res)
+ .unwrap_or_default();
+ self.description = description;
+ self.tags = tags;
+ }
+
+ #[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> {
/// Return the due date of the task, if there is one.
#[must_use]
- pub fn due(&self) -> Option<NaiveDate> {
+ pub fn due(&self) -> Option<&NaiveDate> {
self.tags
.iter()
- .find_map(|t| if let Tag::Due(d) = t { Some(*d) } else { None })
+ .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()
+ .find_map(|t| if let Tag::Rec(r) = t { Some(r) } else { None })
+ }
}
impl<'a>
Err(TodoError::IncorrectFormat(_))
));
}
+
+ #[test]
+ fn test_rec() {
+ let rec = Recurring {
+ strict: false,
+ amount: TimeAmount {
+ amount: 1,
+ unit: TimeUnit::Year,
+ },
+ };
+ let task = Task {
+ completion: Completion::Incomplete,
+ priority: 0,
+ creation_date: None,
+ description: "sample task +project rec:1y",
+ tags: vec![Tag::Project("project"), Tag::Rec(rec.clone())],
+ };
+
+ assert_eq!(task.rec(), Some(&rec));
+ }
}
blob - ee256ea0a92d53e6d320c4cda40e76b284bd50cf
blob + 33bed8d1a6b05432b25add2ec1484285a2d4aa09
--- tests/test.rs
+++ tests/test.rs
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![
+ Tag::Project("todotxt-parser"),
+ Tag::Context("workstation"),
+ Tag::Due(NaiveDate::from_ymd_opt(2023, 1, 4).unwrap()),
+ ];
+ let tags = vec![&tags1];
assert_eq!(
ts,
- 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()),
- ],
- }]
+ vec![Task::new(
+ Completion::Incomplete,
+ 0,
+ Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()),
+ "write tests for todotxt-parser +todotxt-parser @workstation due:2023-01-04"
+ )]
);
+ assert_eq!(ts.iter().map(Task::tags).collect::<Vec<_>>(), tags);
}
#[test]
"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 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,
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 {
- 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())
- ],
- },
+ Task::new(
+ Completion::Incomplete,
+ 0,
+ Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()),
+ "write tests for todotxt-parser +todotxt-parser @workstation due:2023-01-04"
+ ),
+ Task::new(
+ Completion::Complete(Some(NaiveDate::from_ymd_opt(2023, 1, 2).unwrap())),
+ 0,
+ None,
+ "grocery shopping +groceries @supermarket due:2023-01-03"
+ ),
]
);
+ assert_eq!(ts.iter().map(Task::tags).collect::<Vec<_>>(), tags);
}
blob - 4c7155c585e67c7ae402985b0a31e3cb069b1e45
blob + 69a61ac9e58924aa1b196262ccfc2ae3fdb240cd
--- tests/types.rs
+++ tests/types.rs
#[test]
fn test_task_try_from_str() {
let res = Task::try_from(TASK_STR).unwrap();
+ let tags = vec![
+ Tag::Project("chapelShelving"),
+ Tag::Context("chapel"),
+ Tag::Due(NaiveDate::from_ymd_opt(2016, 5, 30).unwrap()),
+ ];
assert_eq!(
res,
- 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())
- ]
- }
+ Task::new(
+ Completion::Complete(Some(NaiveDate::from_ymd_opt(2016, 5, 20).unwrap())),
+ 1,
+ Some(NaiveDate::from_ymd_opt(2016, 4, 30).unwrap()),
+ "measure space for +chapelShelving @chapel due:2016-05-30"
+ )
);
+ assert_eq!(res.tags(), &tags);
}