add dependency support to taskchampion
This commit is contained in:
@@ -520,6 +520,43 @@ static void test_task_udas(void) {
|
|||||||
tc_replica_free(rep);
|
tc_replica_free(rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dependency manipulation
|
||||||
|
static void test_task_dependencies(void) {
|
||||||
|
TCReplica *rep = tc_replica_new_in_memory();
|
||||||
|
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
|
||||||
|
|
||||||
|
TCTask *task1 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task 1"));
|
||||||
|
TEST_ASSERT_NOT_NULL(task1);
|
||||||
|
TCTask *task2 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task 2"));
|
||||||
|
TEST_ASSERT_NOT_NULL(task2);
|
||||||
|
|
||||||
|
TCUuidList deps;
|
||||||
|
|
||||||
|
deps = tc_task_get_dependencies(task1);
|
||||||
|
TEST_ASSERT_EQUAL(0, deps.len);
|
||||||
|
tc_uuid_list_free(&deps);
|
||||||
|
|
||||||
|
tc_task_to_mut(task1, rep);
|
||||||
|
TEST_ASSERT_EQUAL(TC_RESULT_OK,
|
||||||
|
tc_task_add_dependency(task1, tc_task_get_uuid(task2)));
|
||||||
|
|
||||||
|
deps = tc_task_get_dependencies(task1);
|
||||||
|
TEST_ASSERT_EQUAL(1, deps.len);
|
||||||
|
TEST_ASSERT_EQUAL_MEMORY(tc_task_get_uuid(task2).bytes, deps.items[0].bytes, 16);
|
||||||
|
tc_uuid_list_free(&deps);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(TC_RESULT_OK,
|
||||||
|
tc_task_remove_dependency(task1, tc_task_get_uuid(task2)));
|
||||||
|
|
||||||
|
deps = tc_task_get_dependencies(task1);
|
||||||
|
TEST_ASSERT_EQUAL(0, deps.len);
|
||||||
|
tc_uuid_list_free(&deps);
|
||||||
|
|
||||||
|
tc_task_free(task1);
|
||||||
|
tc_task_free(task2);
|
||||||
|
tc_replica_free(rep);
|
||||||
|
}
|
||||||
|
|
||||||
static void tckvlist_assert_key(TCKVList *list, char *key, char *value) {
|
static void tckvlist_assert_key(TCKVList *list, char *key, char *value) {
|
||||||
TEST_ASSERT_NOT_NULL(list);
|
TEST_ASSERT_NOT_NULL(list);
|
||||||
for (size_t i = 0; i < list->len; i++) {
|
for (size_t i = 0; i < list->len; i++) {
|
||||||
@@ -624,6 +661,7 @@ int task_tests(void) {
|
|||||||
RUN_TEST(test_task_get_tags);
|
RUN_TEST(test_task_get_tags);
|
||||||
RUN_TEST(test_task_annotations);
|
RUN_TEST(test_task_annotations);
|
||||||
RUN_TEST(test_task_udas);
|
RUN_TEST(test_task_udas);
|
||||||
|
RUN_TEST(test_task_dependencies);
|
||||||
RUN_TEST(test_task_taskmap);
|
RUN_TEST(test_task_taskmap);
|
||||||
RUN_TEST(test_task_list_take);
|
RUN_TEST(test_task_list_take);
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use std::ops::Deref;
|
|||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use taskchampion::chrono::{TimeZone, Utc};
|
use taskchampion::chrono::{TimeZone, Utc};
|
||||||
use taskchampion::{Annotation, Tag, Task, TaskMut};
|
use taskchampion::{Annotation, Tag, Task, TaskMut, Uuid};
|
||||||
|
|
||||||
/// A task, as publicly exposed by this library.
|
/// A task, as publicly exposed by this library.
|
||||||
///
|
///
|
||||||
@@ -790,6 +790,56 @@ pub unsafe extern "C" fn tc_task_remove_legacy_uda(task: *mut TCTask, key: TCStr
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all dependencies for a task.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn tc_task_get_dependencies(task: *mut TCTask) -> TCUuidList {
|
||||||
|
wrap(task, |task| {
|
||||||
|
let vec: Vec<TCUuid> = task
|
||||||
|
.get_dependencies()
|
||||||
|
.map(|u| {
|
||||||
|
// SAFETY:
|
||||||
|
// - value is not allocated
|
||||||
|
unsafe { TCUuid::return_val(u) }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
// SAFETY:
|
||||||
|
// - caller will free this list
|
||||||
|
unsafe { TCUuidList::return_val(vec) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a dependency.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn tc_task_add_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult {
|
||||||
|
// SAFETY:
|
||||||
|
// - tcuuid is a valid TCUuid (all byte patterns are valid)
|
||||||
|
let dep: Uuid = unsafe { TCUuid::val_from_arg(dep) };
|
||||||
|
wrap_mut(
|
||||||
|
task,
|
||||||
|
|task| {
|
||||||
|
task.add_dependency(dep)?;
|
||||||
|
Ok(TCResult::Ok)
|
||||||
|
},
|
||||||
|
TCResult::Error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a dependency.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn tc_task_remove_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult {
|
||||||
|
// SAFETY:
|
||||||
|
// - tcuuid is a valid TCUuid (all byte patterns are valid)
|
||||||
|
let dep: Uuid = unsafe { TCUuid::val_from_arg(dep) };
|
||||||
|
wrap_mut(
|
||||||
|
task,
|
||||||
|
|task| {
|
||||||
|
task.remove_dependency(dep)?;
|
||||||
|
Ok(TCResult::Ok)
|
||||||
|
},
|
||||||
|
TCResult::Error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the latest error for a task, or a string NULL ptr field if the last operation succeeded.
|
/// Get the latest error for a task, or a string NULL ptr field if the last operation succeeded.
|
||||||
/// Subsequent calls to this function will return NULL. The task pointer must not be NULL. The
|
/// Subsequent calls to this function will return NULL. The task pointer must not be NULL. The
|
||||||
/// caller must free the returned string.
|
/// caller must free the returned string.
|
||||||
|
|||||||
@@ -911,6 +911,21 @@ TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct
|
|||||||
*/
|
*/
|
||||||
TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key);
|
TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all dependencies for a task.
|
||||||
|
*/
|
||||||
|
struct TCUuidList tc_task_get_dependencies(struct TCTask *task);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a dependency.
|
||||||
|
*/
|
||||||
|
TCResult tc_task_add_dependency(struct TCTask *task, struct TCUuid dep);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a dependency.
|
||||||
|
*/
|
||||||
|
TCResult tc_task_remove_dependency(struct TCTask *task, struct TCUuid dep);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the latest error for a task, or a string NULL ptr field if the last operation succeeded.
|
* Get the latest error for a task, or a string NULL ptr field if the last operation succeeded.
|
||||||
* Subsequent calls to this function will return NULL. The task pointer must not be NULL. The
|
* Subsequent calls to this function will return NULL. The task pointer must not be NULL. The
|
||||||
|
|||||||
@@ -243,10 +243,27 @@ impl Task {
|
|||||||
.map(|(p, v)| (p.as_ref(), v.as_ref()))
|
.map(|(p, v)| (p.as_ref(), v.as_ref()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the modification time for this task.
|
||||||
pub fn get_modified(&self) -> Option<DateTime<Utc>> {
|
pub fn get_modified(&self) -> Option<DateTime<Utc>> {
|
||||||
self.get_timestamp(Prop::Modified.as_ref())
|
self.get_timestamp(Prop::Modified.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the UUIDs of tasks on which this task depends.
|
||||||
|
///
|
||||||
|
/// This includes all dependencies, regardless of their status. In fact, it may include
|
||||||
|
/// dependencies that do not exist.
|
||||||
|
pub fn get_dependencies(&self) -> impl Iterator<Item = Uuid> + '_ {
|
||||||
|
self.taskmap.iter().filter_map(|(p, _)| {
|
||||||
|
if let Some(dep_str) = p.strip_prefix("dep_") {
|
||||||
|
if let Ok(u) = Uuid::parse_str(dep_str) {
|
||||||
|
return Some(u);
|
||||||
|
}
|
||||||
|
// (un-parseable dep_.. properties are ignored)
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// -- utility functions
|
// -- utility functions
|
||||||
|
|
||||||
fn is_known_key(key: &str) -> bool {
|
fn is_known_key(key: &str) -> bool {
|
||||||
@@ -423,6 +440,18 @@ impl<'r> TaskMut<'r> {
|
|||||||
self.set_string(key, None)
|
self.set_string(key, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a dependency.
|
||||||
|
pub fn add_dependency(&mut self, dep: Uuid) -> anyhow::Result<()> {
|
||||||
|
let key = format!("dep_{}", dep);
|
||||||
|
self.set_string(key, Some("".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a dependency.
|
||||||
|
pub fn remove_dependency(&mut self, dep: Uuid) -> anyhow::Result<()> {
|
||||||
|
let key = format!("dep_{}", dep);
|
||||||
|
self.set_string(key, None)
|
||||||
|
}
|
||||||
|
|
||||||
// -- utility functions
|
// -- utility functions
|
||||||
|
|
||||||
fn update_modified(&mut self) -> anyhow::Result<()> {
|
fn update_modified(&mut self) -> anyhow::Result<()> {
|
||||||
@@ -1011,4 +1040,25 @@ mod test {
|
|||||||
assert!(task.remove_legacy_uda("tag_abc").is_err());
|
assert!(task.remove_legacy_uda("tag_abc").is_err());
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dependencies() {
|
||||||
|
with_mut_task(|mut task| {
|
||||||
|
assert_eq!(task.get_dependencies().collect::<Vec<_>>(), vec![]);
|
||||||
|
let dep1 = Uuid::new_v4();
|
||||||
|
let dep2 = Uuid::new_v4();
|
||||||
|
|
||||||
|
task.add_dependency(dep1).unwrap();
|
||||||
|
assert_eq!(task.get_dependencies().collect::<Vec<_>>(), vec![dep1]);
|
||||||
|
|
||||||
|
task.add_dependency(dep1).unwrap(); // add twice is ok
|
||||||
|
task.add_dependency(dep2).unwrap();
|
||||||
|
let deps = task.get_dependencies().collect::<Vec<_>>();
|
||||||
|
assert!(deps.contains(&dep1));
|
||||||
|
assert!(deps.contains(&dep2));
|
||||||
|
|
||||||
|
task.remove_dependency(dep1).unwrap();
|
||||||
|
assert_eq!(task.get_dependencies().collect::<Vec<_>>(), vec![dep2]);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user