mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-24 18:06:42 +02:00
more task functionality
This commit is contained in:
parent
821118106a
commit
bb722325fe
10 changed files with 333 additions and 24 deletions
|
@ -3,7 +3,7 @@ INC=-I ../lib
|
|||
LIB=-L ../target/debug
|
||||
RPATH=-Wl,-rpath,../target/debug
|
||||
|
||||
TESTS = replica.cpp uuid.cpp
|
||||
TESTS = replica.cpp uuid.cpp task.cpp
|
||||
|
||||
.PHONY: all test
|
||||
|
||||
|
|
35
binding-tests/task.cpp
Normal file
35
binding-tests/task.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include <string.h>
|
||||
#include "doctest.h"
|
||||
#include "taskchampion.h"
|
||||
|
||||
TEST_CASE("creating a Task does not crash") {
|
||||
TCReplica *rep = tc_replica_new(NULL);
|
||||
CHECK(tc_replica_error(rep) == NULL);
|
||||
|
||||
TCTask *task = tc_replica_new_task(
|
||||
rep,
|
||||
TC_STATUS_PENDING,
|
||||
tc_string_new("my task"));
|
||||
REQUIRE(task != NULL);
|
||||
|
||||
CHECK(tc_task_get_status(task) == TC_STATUS_PENDING);
|
||||
|
||||
TCString *desc = tc_task_get_description(task);
|
||||
REQUIRE(desc != NULL);
|
||||
CHECK(strcmp(tc_string_content(desc), "my task") == 0);
|
||||
tc_string_free(desc);
|
||||
|
||||
tc_task_free(task);
|
||||
|
||||
tc_replica_free(rep);
|
||||
}
|
||||
|
||||
TEST_CASE("undo on an empty in-memory TCReplica does nothing") {
|
||||
TCReplica *rep = tc_replica_new(NULL);
|
||||
CHECK(tc_replica_error(rep) == NULL);
|
||||
int rv = tc_replica_undo(rep);
|
||||
CHECK(rv == 0);
|
||||
CHECK(tc_replica_error(rep) == NULL);
|
||||
tc_replica_free(rep);
|
||||
}
|
||||
|
|
@ -10,6 +10,11 @@ fn main() {
|
|||
.with_language(Language::C)
|
||||
.with_config(Config {
|
||||
cpp_compat: true,
|
||||
enumeration: EnumConfig {
|
||||
// this appears to still default to true for C
|
||||
enum_class: false,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.generate()
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
pub mod replica;
|
||||
pub mod status;
|
||||
pub mod string;
|
||||
pub mod task;
|
||||
pub mod uuid;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::{status::TCStatus, string::TCString, task::TCTask};
|
||||
use libc::c_char;
|
||||
use std::ffi::{CStr, CString, OsStr};
|
||||
use std::path::PathBuf;
|
||||
|
@ -9,11 +10,37 @@ use std::os::unix::ffi::OsStrExt;
|
|||
/// A replica represents an instance of a user's task data, providing an easy interface
|
||||
/// for querying and modifying that data.
|
||||
pub struct TCReplica {
|
||||
// TODO: make this an option so that it can be take()n when holding a mut ref
|
||||
// TODO: make this a RefCell so that it can be take()n when holding a mut ref
|
||||
inner: Replica,
|
||||
error: Option<CString>,
|
||||
}
|
||||
|
||||
/// Utility function to safely convert *mut TCReplica into &mut TCReplica
|
||||
fn rep_ref(rep: *mut TCReplica) -> &'static mut TCReplica {
|
||||
debug_assert!(!rep.is_null());
|
||||
unsafe { &mut *rep }
|
||||
}
|
||||
|
||||
/// Utility function to allow using `?` notation to return an error value.
|
||||
fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T
|
||||
where
|
||||
F: FnOnce(&mut Replica) -> anyhow::Result<T>,
|
||||
{
|
||||
let rep: &'a mut TCReplica = rep_ref(rep);
|
||||
match f(&mut rep.inner) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let error = e.to_string();
|
||||
let error = match CString::new(error.as_bytes()) {
|
||||
Ok(e) => e,
|
||||
Err(_) => CString::new("(invalid error message)".as_bytes()).unwrap(),
|
||||
};
|
||||
rep.error = Some(error);
|
||||
err_value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new TCReplica.
|
||||
///
|
||||
/// If path is NULL, then an in-memory replica is created. Otherwise, path is the path to the
|
||||
|
@ -45,30 +72,37 @@ pub extern "C" fn tc_replica_new<'a>(path: *const c_char) -> *mut TCReplica {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Utility function to safely convert *mut TCReplica into &mut TCReplica
|
||||
fn rep_ref(rep: *mut TCReplica) -> &'static mut TCReplica {
|
||||
debug_assert!(!rep.is_null());
|
||||
unsafe { &mut *rep }
|
||||
/*
|
||||
* TODO:
|
||||
* - tc_replica_all_tasks
|
||||
* - tc_replica_all_task_uuids
|
||||
* - tc_replica_working_set
|
||||
* - tc_replica_get_task
|
||||
*/
|
||||
|
||||
/// Create a new task. The task must not already exist.
|
||||
///
|
||||
/// Returns the task, or NULL on error.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tc_replica_new_task<'a>(
|
||||
rep: *mut TCReplica,
|
||||
status: TCStatus,
|
||||
description: *mut TCString,
|
||||
) -> *mut TCTask {
|
||||
wrap(
|
||||
rep,
|
||||
|rep| {
|
||||
let description = TCString::from_arg(description);
|
||||
let task = rep.new_task(status.into(), description.as_str()?.to_string())?;
|
||||
Ok(TCTask::as_ptr(task))
|
||||
},
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
}
|
||||
|
||||
fn wrap<'a, T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T
|
||||
where
|
||||
F: FnOnce(&mut Replica) -> anyhow::Result<T>,
|
||||
{
|
||||
let rep: &'a mut TCReplica = rep_ref(rep);
|
||||
match f(&mut rep.inner) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let error = e.to_string();
|
||||
let error = match CString::new(error.as_bytes()) {
|
||||
Ok(e) => e,
|
||||
Err(_) => CString::new("(invalid error message)".as_bytes()).unwrap(),
|
||||
};
|
||||
rep.error = Some(error);
|
||||
err_value
|
||||
}
|
||||
}
|
||||
}
|
||||
/* - tc_replica_import_task_with_uuid
|
||||
* - tc_replica_sync
|
||||
*/
|
||||
|
||||
/// Undo local operations until the most recent UndoPoint.
|
||||
///
|
||||
|
@ -95,5 +129,11 @@ pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *const c_char {
|
|||
/// Free a TCReplica.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tc_replica_free(rep: *mut TCReplica) {
|
||||
debug_assert!(!rep.is_null());
|
||||
drop(unsafe { Box::from_raw(rep) });
|
||||
}
|
||||
|
||||
/*
|
||||
* - tc_replica_rebuild_working_set
|
||||
* - tc_replica_add_undo_point
|
||||
*/
|
||||
|
|
36
lib/src/status.rs
Normal file
36
lib/src/status.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
pub use taskchampion::Status;
|
||||
|
||||
/// The status of a task, as defined by the task data model.
|
||||
/// cbindgen:prefix-with-name
|
||||
/// cbindgen:rename-all=ScreamingSnakeCase
|
||||
#[repr(C)]
|
||||
pub enum TCStatus {
|
||||
Pending,
|
||||
Completed,
|
||||
Deleted,
|
||||
/// Unknown signifies a status in the task DB that was not
|
||||
/// recognized.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<TCStatus> for Status {
|
||||
fn from(status: TCStatus) -> Status {
|
||||
match status {
|
||||
TCStatus::Pending => Status::Pending,
|
||||
TCStatus::Completed => Status::Completed,
|
||||
TCStatus::Deleted => Status::Deleted,
|
||||
TCStatus::Unknown => Status::Unknown("unknown".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Status> for TCStatus {
|
||||
fn from(status: Status) -> TCStatus {
|
||||
match status {
|
||||
Status::Pending => TCStatus::Pending,
|
||||
Status::Completed => TCStatus::Completed,
|
||||
Status::Deleted => TCStatus::Deleted,
|
||||
Status::Unknown(_) => TCStatus::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
48
lib/src/string.rs
Normal file
48
lib/src/string.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use std::ffi::{CStr, CString, NulError};
|
||||
|
||||
// thinking:
|
||||
// - TCString ownership always taken when passed in
|
||||
// - TCString ownership always falls to C when passed out
|
||||
// - accept that bytes must be copied to get owned string
|
||||
// - Can we do this with an enum of some sort?
|
||||
|
||||
/// TCString supports passing strings into and out of the TaskChampion API.
|
||||
pub struct TCString(CString);
|
||||
|
||||
impl TCString {
|
||||
/// Take a TCString from C as an argument.
|
||||
pub(crate) fn from_arg(tcstring: *mut TCString) -> Self {
|
||||
debug_assert!(!tcstring.is_null());
|
||||
*(unsafe { Box::from_raw(tcstring) })
|
||||
}
|
||||
|
||||
/// Get a regular Rust &str for this value.
|
||||
pub(crate) fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
|
||||
self.0.as_c_str().to_str()
|
||||
}
|
||||
|
||||
/// Construct a *mut TCString from a string for returning to C.
|
||||
pub(crate) fn return_string(string: impl Into<Vec<u8>>) -> Result<*mut TCString, NulError> {
|
||||
let tcstring = TCString(CString::new(string)?);
|
||||
Ok(Box::into_raw(Box::new(tcstring)))
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tc_string_new(cstr: *const libc::c_char) -> *mut TCString {
|
||||
let cstring = unsafe { CStr::from_ptr(cstr) }.into();
|
||||
Box::into_raw(Box::new(TCString(cstring)))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tc_string_content(string: *mut TCString) -> *const libc::c_char {
|
||||
debug_assert!(!string.is_null());
|
||||
let string: &CString = unsafe { &(*string).0 };
|
||||
string.as_ptr()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tc_string_free(string: *mut TCString) {
|
||||
debug_assert!(!string.is_null());
|
||||
drop(unsafe { Box::from_raw(string) });
|
||||
}
|
96
lib/src/task.rs
Normal file
96
lib/src/task.rs
Normal file
|
@ -0,0 +1,96 @@
|
|||
use crate::{status::TCStatus, string::TCString, uuid::TCUuid};
|
||||
use taskchampion::Task;
|
||||
|
||||
/// A task, as publicly exposed by this library.
|
||||
///
|
||||
/// A task carries no reference to the replica that created it, and can
|
||||
/// be used until it is freed or converted to a TaskMut.
|
||||
pub struct TCTask {
|
||||
inner: Task,
|
||||
}
|
||||
|
||||
impl TCTask {
|
||||
pub(crate) fn as_ptr(task: Task) -> *mut TCTask {
|
||||
Box::into_raw(Box::new(TCTask { inner: task }))
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility function to allow using `?` notation to return an error value.
|
||||
fn wrap<'a, T, F>(task: *const TCTask, f: F, err_value: T) -> T
|
||||
where
|
||||
F: FnOnce(&Task) -> anyhow::Result<T>,
|
||||
{
|
||||
let task: &'a Task = task_ref(task);
|
||||
match f(task) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
/*
|
||||
let error = e.to_string();
|
||||
let error = match CString::new(error.as_bytes()) {
|
||||
Ok(e) => e,
|
||||
Err(_) => CString::new("(invalid error message)".as_bytes()).unwrap(),
|
||||
};
|
||||
*/
|
||||
//task.error = Some(error);
|
||||
err_value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility function to safely convert *const TCTask into &Task
|
||||
fn task_ref(task: *const TCTask) -> &'static Task {
|
||||
debug_assert!(!task.is_null());
|
||||
unsafe { &(*task).inner }
|
||||
}
|
||||
|
||||
/// Get a task's UUID.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tc_task_get_uuid<'a>(task: *const TCTask) -> TCUuid {
|
||||
let task: &'a Task = task_ref(task);
|
||||
let uuid = task.get_uuid();
|
||||
uuid.into()
|
||||
}
|
||||
|
||||
/// Get a task's status.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tc_task_get_status<'a>(task: *const TCTask) -> TCStatus {
|
||||
let task: &'a Task = task_ref(task);
|
||||
task.get_status().into()
|
||||
}
|
||||
|
||||
/* TODO
|
||||
* into_mut
|
||||
* get_taskmap
|
||||
*/
|
||||
|
||||
/// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it
|
||||
/// contains embedded NUL characters).
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCString {
|
||||
wrap(
|
||||
task,
|
||||
|task| Ok(TCString::return_string(task.get_description())?),
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
}
|
||||
|
||||
/* TODO
|
||||
* get_wait
|
||||
* is_waiting
|
||||
* is_active
|
||||
* has_tag
|
||||
* get_tags
|
||||
* get_annotations
|
||||
* get_uda
|
||||
* get_udas
|
||||
* get_legacy_uda
|
||||
* get_legacy_udas
|
||||
* get_modified
|
||||
*/
|
||||
|
||||
/// Free a task.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tc_task_free<'a>(task: *mut TCTask) {
|
||||
debug_assert!(!task.is_null());
|
||||
drop(unsafe { Box::from_raw(task) });
|
||||
}
|
|
@ -4,11 +4,32 @@
|
|||
#include <ostream>
|
||||
#include <new>
|
||||
|
||||
/// The status of a task, as defined by the task data model.
|
||||
enum TCStatus {
|
||||
TC_STATUS_PENDING,
|
||||
TC_STATUS_COMPLETED,
|
||||
TC_STATUS_DELETED,
|
||||
/// Unknown signifies a status in the task DB that was not
|
||||
/// recognized.
|
||||
TC_STATUS_UNKNOWN,
|
||||
};
|
||||
|
||||
/// A replica represents an instance of a user's task data, providing an easy interface
|
||||
/// for querying and modifying that data.
|
||||
struct TCReplica;
|
||||
|
||||
/// TCString supports passing strings into and out of the TaskChampion API.
|
||||
struct TCString;
|
||||
|
||||
/// A task, as publicly exposed by this library.
|
||||
///
|
||||
/// A task carries no reference to the replica that created it, and can
|
||||
/// be used until it is freed or converted to a TaskMut.
|
||||
struct TCTask;
|
||||
|
||||
/// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed.
|
||||
/// Uuids are typically treated as opaque, but the bytes are available in big-endian format.
|
||||
///
|
||||
struct TCUuid {
|
||||
uint8_t bytes[16];
|
||||
};
|
||||
|
@ -27,6 +48,11 @@ extern const uintptr_t TC_UUID_STRING_BYTES;
|
|||
/// TCReplicas are not threadsafe.
|
||||
TCReplica *tc_replica_new(const char *path);
|
||||
|
||||
/// Create a new task. The task must not already exist.
|
||||
///
|
||||
/// Returns the task, or NULL on error.
|
||||
TCTask *tc_replica_new_task(TCReplica *rep, TCStatus status, TCString *description);
|
||||
|
||||
/// Undo local operations until the most recent UndoPoint.
|
||||
///
|
||||
/// Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were
|
||||
|
@ -41,6 +67,25 @@ const char *tc_replica_error(TCReplica *rep);
|
|||
/// Free a TCReplica.
|
||||
void tc_replica_free(TCReplica *rep);
|
||||
|
||||
TCString *tc_string_new(const char *cstr);
|
||||
|
||||
const char *tc_string_content(TCString *string);
|
||||
|
||||
void tc_string_free(TCString *string);
|
||||
|
||||
/// Get a task's UUID.
|
||||
TCUuid tc_task_get_uuid(const TCTask *task);
|
||||
|
||||
/// Get a task's status.
|
||||
TCStatus tc_task_get_status(const TCTask *task);
|
||||
|
||||
/// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it
|
||||
/// contains embedded NUL characters).
|
||||
TCString *tc_task_get_description(const TCTask *task);
|
||||
|
||||
/// Free a task.
|
||||
void tc_task_free(TCTask *task);
|
||||
|
||||
/// Create a new, randomly-generated UUID.
|
||||
TCUuid tc_uuid_new_v4();
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/// The status of a task, as defined by the task data model.
|
||||
#[derive(Debug, PartialEq, Clone, strum_macros::Display)]
|
||||
#[repr(C)]
|
||||
pub enum Status {
|
||||
Pending,
|
||||
Completed,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue