mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-23 23:46:42 +02:00
entry and wait time support
This commit is contained in:
parent
f2b3e5fd0a
commit
e5625e1597
8 changed files with 190 additions and 14 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3036,6 +3036,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cbindgen",
|
"cbindgen",
|
||||||
|
"chrono",
|
||||||
"libc",
|
"libc",
|
||||||
"taskchampion",
|
"taskchampion",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
|
|
@ -111,6 +111,67 @@ static void test_task_get_set_description(void) {
|
||||||
tc_replica_free(rep);
|
tc_replica_free(rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updating entry on a task works
|
||||||
|
static void test_task_get_set_entry(void) {
|
||||||
|
TCReplica *rep = tc_replica_new_in_memory();
|
||||||
|
TEST_ASSERT_NULL(tc_replica_error(rep));
|
||||||
|
|
||||||
|
TCTask *task = tc_replica_new_task(
|
||||||
|
rep,
|
||||||
|
TC_STATUS_PENDING,
|
||||||
|
tc_string_borrow("my task"));
|
||||||
|
TEST_ASSERT_NOT_NULL(task);
|
||||||
|
|
||||||
|
// creation of a task sets entry to current time
|
||||||
|
TEST_ASSERT_NOT_EQUAL(0, tc_task_get_entry(task));
|
||||||
|
|
||||||
|
tc_task_to_mut(task, rep);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_entry(task, 1643679997));
|
||||||
|
TEST_ASSERT_EQUAL(1643679997, tc_task_get_entry(task));
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_entry(task, 0));
|
||||||
|
TEST_ASSERT_EQUAL(0, tc_task_get_entry(task));
|
||||||
|
|
||||||
|
tc_task_free(task);
|
||||||
|
|
||||||
|
tc_replica_free(rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
// updating wait on a task works
|
||||||
|
static void test_task_get_set_wait_and_is_waiting(void) {
|
||||||
|
TCReplica *rep = tc_replica_new_in_memory();
|
||||||
|
TEST_ASSERT_NULL(tc_replica_error(rep));
|
||||||
|
|
||||||
|
TCTask *task = tc_replica_new_task(
|
||||||
|
rep,
|
||||||
|
TC_STATUS_PENDING,
|
||||||
|
tc_string_borrow("my task"));
|
||||||
|
TEST_ASSERT_NOT_NULL(task);
|
||||||
|
|
||||||
|
// wait is not set on creation
|
||||||
|
TEST_ASSERT_EQUAL(0, tc_task_get_wait(task));
|
||||||
|
TEST_ASSERT_FALSE(tc_task_is_waiting(task));
|
||||||
|
|
||||||
|
tc_task_to_mut(task, rep);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 3643679997)); // 2085
|
||||||
|
TEST_ASSERT_EQUAL(3643679997, tc_task_get_wait(task));
|
||||||
|
TEST_ASSERT_TRUE(tc_task_is_waiting(task));
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 643679997)); // THE PAST!
|
||||||
|
TEST_ASSERT_EQUAL(643679997, tc_task_get_wait(task));
|
||||||
|
TEST_ASSERT_FALSE(tc_task_is_waiting(task));
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 0));
|
||||||
|
TEST_ASSERT_EQUAL(0, tc_task_get_wait(task));
|
||||||
|
TEST_ASSERT_FALSE(tc_task_is_waiting(task));
|
||||||
|
|
||||||
|
tc_task_free(task);
|
||||||
|
|
||||||
|
tc_replica_free(rep);
|
||||||
|
}
|
||||||
|
|
||||||
// starting and stopping a task works, as seen by tc_task_is_active
|
// starting and stopping a task works, as seen by tc_task_is_active
|
||||||
static void test_task_start_stop_is_active(void) {
|
static void test_task_start_stop_is_active(void) {
|
||||||
TCReplica *rep = tc_replica_new_in_memory();
|
TCReplica *rep = tc_replica_new_in_memory();
|
||||||
|
@ -187,6 +248,8 @@ int task_tests(void) {
|
||||||
RUN_TEST(test_task_free_mutable_task);
|
RUN_TEST(test_task_free_mutable_task);
|
||||||
RUN_TEST(test_task_get_set_status);
|
RUN_TEST(test_task_get_set_status);
|
||||||
RUN_TEST(test_task_get_set_description);
|
RUN_TEST(test_task_get_set_description);
|
||||||
|
RUN_TEST(test_task_get_set_entry);
|
||||||
|
RUN_TEST(test_task_get_set_wait_and_is_waiting);
|
||||||
RUN_TEST(test_task_start_stop_is_active);
|
RUN_TEST(test_task_start_stop_is_active);
|
||||||
RUN_TEST(task_task_add_tag);
|
RUN_TEST(task_task_add_tag);
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
|
|
|
@ -10,8 +10,9 @@ crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2.113"
|
libc = "0.2.113"
|
||||||
|
chrono = "^0.4.10"
|
||||||
taskchampion = { path = "../taskchampion" }
|
taskchampion = { path = "../taskchampion" }
|
||||||
uuid = { version = "^0.8.2", features = ["serde", "v4"] }
|
uuid = { version = "^0.8.2", features = ["v4"] }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
|
@ -10,7 +10,7 @@ fn main() {
|
||||||
.with_config(Config {
|
.with_config(Config {
|
||||||
language: Language::C,
|
language: Language::C,
|
||||||
cpp_compat: true,
|
cpp_compat: true,
|
||||||
sys_includes: vec!["stdbool.h".into(), "stdint.h".into()],
|
sys_includes: vec!["stdbool.h".into(), "stdint.h".into(), "time.h".into()],
|
||||||
usize_is_size_t: true,
|
usize_is_size_t: true,
|
||||||
no_includes: true,
|
no_includes: true,
|
||||||
enumeration: EnumConfig {
|
enumeration: EnumConfig {
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::util::err_to_tcstring;
|
||||||
use crate::{
|
use crate::{
|
||||||
replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, uuid::TCUuid,
|
replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, uuid::TCUuid,
|
||||||
};
|
};
|
||||||
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -13,8 +14,9 @@ use taskchampion::{Tag, Task, TaskMut};
|
||||||
/// to make any changes, and doing so requires exclusive access to the replica
|
/// to make any changes, and doing so requires exclusive access to the replica
|
||||||
/// until the task is freed or converted back to immutable mode.
|
/// until the task is freed or converted back to immutable mode.
|
||||||
///
|
///
|
||||||
/// A task carries no reference to the replica that created it, and can
|
/// An immutable task carries no reference to the replica that created it, and can be used until it
|
||||||
/// be used until it is freed or converted to a TaskMut.
|
/// is freed or converted to a TaskMut. A mutable task carries a reference to the replica and
|
||||||
|
/// must be freed or made immutable before the replica is freed.
|
||||||
///
|
///
|
||||||
/// All `tc_task_..` functions taking a task as an argument require that it not be NULL.
|
/// All `tc_task_..` functions taking a task as an argument require that it not be NULL.
|
||||||
///
|
///
|
||||||
|
@ -178,6 +180,22 @@ impl TryFrom<TCString<'_>> for Tag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a DateTime<Utc> to a libc::time_t, or zero if not set.
|
||||||
|
fn to_time_t(timestamp: Option<DateTime<Utc>>) -> libc::time_t {
|
||||||
|
timestamp
|
||||||
|
.map(|ts| ts.timestamp() as libc::time_t)
|
||||||
|
.unwrap_or(0 as libc::time_t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a libc::time_t to Option<DateTime<Utc>>, treating time zero as None
|
||||||
|
fn to_datetime(time: libc::time_t) -> Option<DateTime<Utc>> {
|
||||||
|
if time == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Utc.timestamp(time as i64, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert an immutable task into a mutable task.
|
/// Convert an immutable task into a mutable task.
|
||||||
///
|
///
|
||||||
/// The task must not be NULL. It is modified in-place, and becomes mutable.
|
/// The task must not be NULL. It is modified in-place, and becomes mutable.
|
||||||
|
@ -245,14 +263,29 @@ pub extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut TCStrin
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tc_task_get_entry
|
/// Get the entry timestamp for a task (when it was created), or 0 if not set.
|
||||||
// TODO: tc_task_get_wait
|
#[no_mangle]
|
||||||
|
pub extern "C" fn tc_task_get_entry<'a>(task: *mut TCTask) -> libc::time_t {
|
||||||
|
wrap(task, |task| to_time_t(task.get_entry()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the wait timestamp for a task, or 0 if not set.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn tc_task_get_wait<'a>(task: *mut TCTask) -> libc::time_t {
|
||||||
|
wrap(task, |task| to_time_t(task.get_wait()))
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: tc_task_get_modified
|
// TODO: tc_task_get_modified
|
||||||
// TODO: tc_task_is_waiting
|
|
||||||
|
/// Check if a task is waiting.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn tc_task_is_waiting(task: *mut TCTask) -> bool {
|
||||||
|
wrap(task, |task| task.is_waiting())
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if a task is active (started and not stopped).
|
/// Check if a task is active (started and not stopped).
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn tc_task_is_active<'a>(task: *mut TCTask) -> bool {
|
pub extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool {
|
||||||
wrap(task, |task| task.is_active())
|
wrap(task, |task| task.is_active())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,7 +347,34 @@ pub extern "C" fn tc_task_set_description<'a>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tc_task_set_entry
|
/// Set a mutable task's entry (creation time). Pass entry=0 to unset
|
||||||
|
/// the entry field.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn tc_task_set_entry<'a>(task: *mut TCTask, entry: libc::time_t) -> TCResult {
|
||||||
|
wrap_mut(
|
||||||
|
task,
|
||||||
|
|task| {
|
||||||
|
task.set_entry(to_datetime(entry))?;
|
||||||
|
Ok(TCResult::Ok)
|
||||||
|
},
|
||||||
|
TCResult::Error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a mutable task's wait (creation time). Pass wait=0 to unset the
|
||||||
|
/// wait field.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn tc_task_set_wait<'a>(task: *mut TCTask, wait: libc::time_t) -> TCResult {
|
||||||
|
wrap_mut(
|
||||||
|
task,
|
||||||
|
|task| {
|
||||||
|
task.set_wait(to_datetime(wait))?;
|
||||||
|
Ok(TCResult::Ok)
|
||||||
|
},
|
||||||
|
TCResult::Error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: tc_task_set_wait
|
// TODO: tc_task_set_wait
|
||||||
// TODO: tc_task_set_modified
|
// TODO: tc_task_set_modified
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Length, in bytes, of the string representation of a UUID (without NUL terminator)
|
* Length, in bytes, of the string representation of a UUID (without NUL terminator)
|
||||||
|
@ -74,8 +75,9 @@ typedef struct TCString TCString;
|
||||||
* to make any changes, and doing so requires exclusive access to the replica
|
* to make any changes, and doing so requires exclusive access to the replica
|
||||||
* until the task is freed or converted back to immutable mode.
|
* until the task is freed or converted back to immutable mode.
|
||||||
*
|
*
|
||||||
* A task carries no reference to the replica that created it, and can
|
* An immutable task carries no reference to the replica that created it, and can be used until it
|
||||||
* be used until it is freed or converted to a TaskMut.
|
* is freed or converted to a TaskMut. A mutable task carries a reference to the replica and
|
||||||
|
* must be freed or made immutable before the replica is freed.
|
||||||
*
|
*
|
||||||
* All `tc_task_..` functions taking a task as an argument require that it not be NULL.
|
* All `tc_task_..` functions taking a task as an argument require that it not be NULL.
|
||||||
*
|
*
|
||||||
|
@ -264,6 +266,21 @@ enum TCStatus tc_task_get_status(struct TCTask *task);
|
||||||
*/
|
*/
|
||||||
struct TCString *tc_task_get_description(struct TCTask *task);
|
struct TCString *tc_task_get_description(struct TCTask *task);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the entry timestamp for a task (when it was created), or 0 if not set.
|
||||||
|
*/
|
||||||
|
time_t tc_task_get_entry(struct TCTask *task);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the wait timestamp for a task, or 0 if not set.
|
||||||
|
*/
|
||||||
|
time_t tc_task_get_wait(struct TCTask *task);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a task is waiting.
|
||||||
|
*/
|
||||||
|
bool tc_task_is_waiting(struct TCTask *task);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a task is active (started and not stopped).
|
* Check if a task is active (started and not stopped).
|
||||||
*/
|
*/
|
||||||
|
@ -285,6 +302,18 @@ TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status);
|
||||||
*/
|
*/
|
||||||
TCResult tc_task_set_description(struct TCTask *task, struct TCString *description);
|
TCResult tc_task_set_description(struct TCTask *task, struct TCString *description);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a mutable task's entry (creation time). Pass entry=0 to unset
|
||||||
|
* the entry field.
|
||||||
|
*/
|
||||||
|
TCResult tc_task_set_entry(struct TCTask *task, time_t entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a mutable task's wait (creation time). Pass wait=0 to unset the
|
||||||
|
* wait field.
|
||||||
|
*/
|
||||||
|
TCResult tc_task_set_wait(struct TCTask *task, time_t wait);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a task.
|
* Start a task.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -110,7 +110,7 @@ impl Replica {
|
||||||
let mut task = Task::new(uuid, taskmap).into_mut(self);
|
let mut task = Task::new(uuid, taskmap).into_mut(self);
|
||||||
task.set_description(description)?;
|
task.set_description(description)?;
|
||||||
task.set_status(status)?;
|
task.set_status(status)?;
|
||||||
task.set_entry(Utc::now())?;
|
task.set_entry(Some(Utc::now()))?;
|
||||||
trace!("task {} created", uuid);
|
trace!("task {} created", uuid);
|
||||||
Ok(task.into_immut())
|
Ok(task.into_immut())
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,10 @@ impl Task {
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_entry(&self) -> Option<DateTime<Utc>> {
|
||||||
|
self.get_timestamp(Prop::Entry.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_priority(&self) -> Priority {
|
pub fn get_priority(&self) -> Priority {
|
||||||
self.taskmap
|
self.taskmap
|
||||||
.get(Prop::Status.as_ref())
|
.get(Prop::Status.as_ref())
|
||||||
|
@ -299,8 +303,8 @@ impl<'r> TaskMut<'r> {
|
||||||
self.set_string(Prop::Description.as_ref(), Some(description))
|
self.set_string(Prop::Description.as_ref(), Some(description))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_entry(&mut self, entry: DateTime<Utc>) -> anyhow::Result<()> {
|
pub fn set_entry(&mut self, entry: Option<DateTime<Utc>>) -> anyhow::Result<()> {
|
||||||
self.set_timestamp(Prop::Entry.as_ref(), Some(entry))
|
self.set_timestamp(Prop::Entry.as_ref(), entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_wait(&mut self, wait: Option<DateTime<Utc>>) -> anyhow::Result<()> {
|
pub fn set_wait(&mut self, wait: Option<DateTime<Utc>>) -> anyhow::Result<()> {
|
||||||
|
@ -526,6 +530,24 @@ mod test {
|
||||||
assert!(!task.is_active());
|
assert!(!task.is_active());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_entry_not_set() {
|
||||||
|
let task = Task::new(Uuid::new_v4(), TaskMap::new());
|
||||||
|
assert_eq!(task.get_entry(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_entry_set() {
|
||||||
|
let ts = Utc.ymd(1980, 1, 1).and_hms(0, 0, 0);
|
||||||
|
let task = Task::new(
|
||||||
|
Uuid::new_v4(),
|
||||||
|
vec![(String::from("entry"), format!("{}", ts.timestamp()))]
|
||||||
|
.drain(..)
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
assert_eq!(task.get_entry(), Some(ts));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wait_not_set() {
|
fn test_wait_not_set() {
|
||||||
let task = Task::new(Uuid::new_v4(), TaskMap::new());
|
let task = Task::new(Uuid::new_v4(), TaskMap::new());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue