From 255cf29d4f21973816938e6eb8c47d903be94fc4 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 26 Sep 2021 13:59:04 -0400 Subject: [PATCH 1/2] Update docs * improve linking * parallel construction for storage and servers * clarify rationale for Task/TaskMut --- taskchampion/src/lib.rs | 12 +++++++++++- taskchampion/src/replica.rs | 5 ++++- taskchampion/src/task/mod.rs | 2 +- taskchampion/src/task/tag.rs | 3 ++- taskchampion/src/task/task.rs | 26 ++++++++++++++++++++------ 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 665fe3a64..df72bdb22 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -3,16 +3,26 @@ This crate implements the core of TaskChampion, the [replica](crate::Replica). +Users of this crate can manipulate a task database using this API, including synchronizing that task database with others via a synchronization server. + +Example uses of this crate: + * user interfaces for task management, such as mobile apps, web apps, or command-line interfaces + * integrations for task management, such as synchronization with ticket-tracking systems or + request forms. + # Replica A TaskChampion replica is a local copy of a user's task data. As the name suggests, several replicas of the same data can exist (such as on a user's laptop and on their phone) and can synchronize with one another. -Replicas are accessed using the [`Replica`](crate::replica) type. +Replicas are accessed using the [`Replica`](crate::Replica) type. # Task Storage +Replicas access the task database via a [storage object](crate::storage::Storage). +Create a storage object with [`StorageConfig`](crate::storage::StorageConfig). + The [`storage`](crate::storage) module supports pluggable storage for a replica's data. An implementation is provided, but users of this crate can provide their own implementation as well. diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index 361476951..46d8c3c6b 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -16,7 +16,10 @@ use uuid::Uuid; /// ## Tasks /// /// Tasks are uniquely identified by UUIDs. -/// Most task modifications are performed via the [`crate::Task`] and [`crate::TaskMut`] types. +/// Most task modifications are performed via the [`Task`](crate::Task) and +/// [`TaskMut`](crate::TaskMut) types. Use of two types for tasks allows easy +/// read-only manipulation of lots of tasks, with exclusive access required only +/// for modifications. /// /// ## Working Set /// diff --git a/taskchampion/src/task/mod.rs b/taskchampion/src/task/mod.rs index ecb755c29..bd0808015 100644 --- a/taskchampion/src/task/mod.rs +++ b/taskchampion/src/task/mod.rs @@ -10,7 +10,7 @@ mod task; pub use annotation::Annotation; pub use priority::Priority; pub use status::Status; -pub use tag::{Tag, INVALID_TAG_CHARACTERS}; +pub use tag::Tag; pub use task::{Task, TaskMut}; pub type Timestamp = DateTime; diff --git a/taskchampion/src/task/tag.rs b/taskchampion/src/task/tag.rs index d3a4842e7..8c6fb4254 100644 --- a/taskchampion/src/task/tag.rs +++ b/taskchampion/src/task/tag.rs @@ -5,7 +5,7 @@ use std::str::FromStr; /// A Tag is a descriptor for a task, that is either present or absent, and can be used for /// filtering. Tags composed of all uppercase letters are reserved for synthetic tags. /// -/// Valid tags must not contain whitespace or any of the characters in [`INVALID_TAG_CHARACTERS`]. +/// Valid tags must not contain whitespace or any of the characters in `+-*/(<>^! %=~`. /// The first characters additionally cannot be a digit, and subsequent characters cannot be `:`. /// This definition is based on [that of /// TaskWarrior](https://github.com/GothenburgBitFactory/taskwarrior/blob/663c6575ceca5bd0135ae884879339dac89d3142/src/Lexer.cpp#L146-L164). @@ -19,6 +19,7 @@ pub(super) enum TagInner { Synthetic(SyntheticTag), } +// see doc comment for Tag, above pub const INVALID_TAG_CHARACTERS: &str = "+-*/(<>^! %=~"; impl Tag { diff --git a/taskchampion/src/task/task.rs b/taskchampion/src/task/task.rs index 40b2d6b0f..27824b860 100644 --- a/taskchampion/src/task/task.rs +++ b/taskchampion/src/task/task.rs @@ -8,6 +8,16 @@ use std::convert::AsRef; use std::convert::TryInto; use uuid::Uuid; +/* The Task and TaskMut classes wrap the underlying [`TaskMap`], which is a simple key/value map. + * They provide semantic meaning to that TaskMap according to the TaskChampion data model. For + * example, [`get_status`](Task::get_status) and [`set_status`](TaskMut::set_status) translate from + * strings in the TaskMap to [`Status`]. + * + * The same approach applies for more complex data such as dependencies or annotations. Users of + * this API should only need the [`get_taskmap`](Task::get_taskmap) method for debugging purposes, + * and should never need to make changes to the TaskMap directly. + */ + /// A task, as publicly exposed by this crate. /// /// Note that Task objects represent a snapshot of the task at a moment in time, and are not @@ -15,16 +25,21 @@ use uuid::Uuid; /// but a Task that is cached for more than a few seconds may cause the user to see stale /// data. Fetch, use, and drop Tasks quickly. /// -/// This struct contains only getters for various values on the task. The `into_mut` method returns -/// a TaskMut which can be used to modify the task. +/// This struct contains only getters for various values on the task. The +/// [`into_mut`](Task::into_mut) method +/// returns a TaskMut which can be used to modify the task. #[derive(Debug, Clone, PartialEq)] pub struct Task { uuid: Uuid, taskmap: TaskMap, } -/// A mutable task, with setter methods. Most methods are simple setters and not further -/// described. Calling a setter will update the Replica, as well as the included Task. +/// A mutable task, with setter methods. +/// +/// Most methods are simple setters and not further described. Calling a setter will update the +/// referenced Replica, as well as the included Task, immediately. +/// +/// The [`Task`] methods are available on [`TaskMut`] via [`Deref`](std::ops::Deref). pub struct TaskMut<'r> { task: Task, replica: &'r mut Replica, @@ -150,8 +165,7 @@ impl Task { } impl<'r> TaskMut<'r> { - /// Get the immutable version of this object. Note that TaskMut [`std::ops::Deref`]s to - /// [`crate::task::Task`], so all of that struct's getter methods can be used on TaskMut. + /// Get the immutable version of this object, ending the exclusive reference to the Replica. pub fn into_immut(self) -> Task { self.task } From a76d7580ce045657d9d536bb1750f5a42805131e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 26 Sep 2021 13:59:58 -0400 Subject: [PATCH 2/2] cargo fmt --- cli/src/argparse/args/time.rs | 36 ++++++++++++++++++++---------- taskchampion/src/storage/sqlite.rs | 13 +++++++---- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/cli/src/argparse/args/time.rs b/cli/src/argparse/args/time.rs index 47a82e1fc..5d81b7abc 100644 --- a/cli/src/argparse/args/time.rs +++ b/cli/src/argparse/args/time.rs @@ -160,12 +160,24 @@ fn named_date( "today" => Ok((remaining, local_today)), "tomorrow" => Ok((remaining, local_today + Duration::days(1))), // TODO: lots more! - "eod" => Ok((remaining,local_today + Duration::days(1))), - "sod" => Ok((remaining,local_today)), - "eow" => Ok((remaining,local_today + Duration::days((6-day_index).into()))), - "eoww" => Ok((remaining,local_today + Duration::days((5-day_index).into()))), - "sow" => Ok((remaining,local_today + Duration::days((6-day_index).into()))), - "soww" => Ok((remaining,local_today + Duration::days((7-day_index).into()))), + "eod" => Ok((remaining, local_today + Duration::days(1))), + "sod" => Ok((remaining, local_today)), + "eow" => Ok(( + remaining, + local_today + Duration::days((6 - day_index).into()), + )), + "eoww" => Ok(( + remaining, + local_today + Duration::days((5 - day_index).into()), + )), + "sow" => Ok(( + remaining, + local_today + Duration::days((6 - day_index).into()), + )), + "soww" => Ok(( + remaining, + local_today + Duration::days((7 - day_index).into()), + )), _ => Err(Err::Error(Error::new(input, ErrorKind::Tag))), } .map(|(rem, dt)| (rem, dt.and_hms(0, 0, 0).with_timezone(&Utc))) @@ -308,12 +320,12 @@ mod test { #[case::today_from_evening(ldt(2021, 3, 1, 21, 30, 30), "today", ld(2021, 3, 1))] #[case::tomorrow(ld(2021, 3, 1), "tomorrow", ld(2021, 3, 2))] #[case::tomorow_from_evening(ldt(2021, 3, 1, 21, 30, 30), "tomorrow", ld(2021, 3, 2))] - #[case::end_of_week(ld(2021,8,25,), "eow", ld(2021,8,29))] - #[case::end_of_work_week(ld(2021,8,25), "eoww", ld(2021,8,28))] - #[case::start_of_week(ld(2021,8,25), "sow", ld(2021,8,29))] - #[case::start_of_work_week(ld(2021,8,25), "soww", ld(2021,8,30))] - #[case::end_of_today(ld(2021,8,25), "eod", ld(2021,8,26))] - #[case::start_of_today(ld(2021,8,25), "sod", ld(2021,8,25))] + #[case::end_of_week(ld(2021, 8, 25,), "eow", ld(2021, 8, 29))] + #[case::end_of_work_week(ld(2021, 8, 25), "eoww", ld(2021, 8, 28))] + #[case::start_of_week(ld(2021, 8, 25), "sow", ld(2021, 8, 29))] + #[case::start_of_work_week(ld(2021, 8, 25), "soww", ld(2021, 8, 30))] + #[case::end_of_today(ld(2021, 8, 25), "eod", ld(2021, 8, 26))] + #[case::start_of_today(ld(2021, 8, 25), "sod", ld(2021, 8, 25))] fn test_local_timestamp( #[case] now: Box DateTime>, #[values(*IST, *UTC_FO, *HST)] tz: FixedOffset, diff --git a/taskchampion/src/storage/sqlite.rs b/taskchampion/src/storage/sqlite.rs index 0e56479fe..4cd1347ed 100644 --- a/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/src/storage/sqlite.rs @@ -113,9 +113,11 @@ impl<'t> Txn<'t> { fn get_next_working_set_number(&self) -> anyhow::Result { let t = self.get_txn()?; let next_id: Option = t - .query_row("SELECT COALESCE(MAX(id), 0) + 1 FROM working_set", [], |r| { - r.get(0) - }) + .query_row( + "SELECT COALESCE(MAX(id), 0) + 1 FROM working_set", + [], + |r| r.get(0), + ) .optional() .context("Getting highest working set ID")?; @@ -290,7 +292,10 @@ impl<'t> StorageTxn for Txn<'t> { let rows: Vec> = rows.collect(); let mut res = Vec::with_capacity(rows.len()); - for _ in 0..self.get_next_working_set_number().context("Getting working set number")? { + for _ in 0..self + .get_next_working_set_number() + .context("Getting working set number")? + { res.push(None); } for r in rows {