commit - c6bbc32edce650be04dc40fbc37cbd56dea19819
commit + a6078516315466372617a4c3b72a6b88d04ad76b
blob - af4b5dbd2ab934d82ac95ac79ff2cb7de160e0bf
blob + 619b035850901ba580d8d12a32b873bdb8c815ba
--- src/err.rs
+++ src/err.rs
/// 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
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;
/// 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
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)
}
map(opt(value(true, tag("+"))), |v| v.unwrap_or(false)),
time_amount,
)),
- |(strict, amount)| Recurring::new(strict, amount),
+ |(strict, amount)| Recurring { strict, amount },
)(i)
}
#[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]
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"),
],
))
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())
],
- )
+ }
))
);
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"),
],
- ),
+ },
))
);
}
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}})
],
- ),
+ },
],
))
);
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
/// 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 {
#[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> {
.1;
Ok(Task {
- completed,
+ completion: (completed, completion_date).try_into()?,
priority,
- completion_date,
creation_date,
description,
tags,
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
*/
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() {
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()),
],
- )]
+ }]
);
}
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
*/
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";
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())
]
- )
+ }
);
}