add dependency support to taskchampion
This commit is contained in:
@@ -520,6 +520,43 @@ static void test_task_udas(void) {
|
||||
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) {
|
||||
TEST_ASSERT_NOT_NULL(list);
|
||||
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_annotations);
|
||||
RUN_TEST(test_task_udas);
|
||||
RUN_TEST(test_task_dependencies);
|
||||
RUN_TEST(test_task_taskmap);
|
||||
RUN_TEST(test_task_list_take);
|
||||
return UNITY_END();
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::ops::Deref;
|
||||
use std::ptr::NonNull;
|
||||
use std::str::FromStr;
|
||||
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.
|
||||
///
|
||||
@@ -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.
|
||||
/// Subsequent calls to this function will return NULL. The task pointer must not be NULL. The
|
||||
/// 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);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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()))
|
||||
}
|
||||
|
||||
/// Get the modification time for this task.
|
||||
pub fn get_modified(&self) -> Option<DateTime<Utc>> {
|
||||
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
|
||||
|
||||
fn is_known_key(key: &str) -> bool {
|
||||
@@ -423,6 +440,18 @@ impl<'r> TaskMut<'r> {
|
||||
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
|
||||
|
||||
fn update_modified(&mut self) -> anyhow::Result<()> {
|
||||
@@ -1011,4 +1040,25 @@ mod test {
|
||||
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