implement lists in the same files as singular data

This commit is contained in:
Dustin J. Mitchell 2022-02-11 23:59:22 +00:00
parent 7996a98908
commit af51e0382a
7 changed files with 208 additions and 226 deletions

View file

@ -11,21 +11,15 @@ pub mod replica;
pub mod result; pub mod result;
pub mod status; pub mod status;
pub mod string; pub mod string;
pub mod stringlist;
pub mod task; pub mod task;
pub mod tasklist;
pub mod uuid; pub mod uuid;
pub mod uuidlist;
pub(crate) mod types { pub(crate) mod types {
pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList}; pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList};
pub(crate) use crate::replica::TCReplica; pub(crate) use crate::replica::TCReplica;
pub(crate) use crate::result::TCResult; pub(crate) use crate::result::TCResult;
pub(crate) use crate::status::TCStatus; pub(crate) use crate::status::TCStatus;
pub(crate) use crate::string::TCString; pub(crate) use crate::string::{TCString, TCStringList};
pub(crate) use crate::stringlist::TCStringList; pub(crate) use crate::task::{TCTask, TCTaskList};
pub(crate) use crate::task::TCTask; pub(crate) use crate::uuid::{TCUuid, TCUuidList};
pub(crate) use crate::tasklist::TCTaskList;
pub(crate) use crate::uuid::TCUuid;
pub(crate) use crate::uuidlist::TCUuidList;
} }

View file

@ -2,6 +2,7 @@ use crate::traits::*;
use std::ffi::{CStr, CString, OsStr}; use std::ffi::{CStr, CString, OsStr};
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::ptr::NonNull;
use std::str::Utf8Error; use std::str::Utf8Error;
/// TCString supports passing strings into and out of the TaskChampion API. /// TCString supports passing strings into and out of the TaskChampion API.
@ -136,6 +137,39 @@ impl<'a> From<&str> for TCString<'static> {
} }
} }
/// TCStringList represents a list of strings.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCStringList {
/// number of strings in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// TCStringList representing each string. these remain owned by the TCStringList instance and will
/// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the
/// *TCStringList at indexes 0..len-1 are not NULL.
items: *const NonNull<TCString<'static>>,
}
impl CArray for TCStringList {
type Element = NonNull<TCString<'static>>;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCStringList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Create a new TCString referencing the given C string. The C string must remain valid and /// Create a new TCString referencing the given C string. The C string must remain valid and
/// unchanged until after the TCString is freed. It's typically easiest to ensure this by using a /// unchanged until after the TCString is freed. It's typically easiest to ensure this by using a
/// static string. /// static string.
@ -281,11 +315,42 @@ pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) {
drop(unsafe { TCString::take_from_arg(tcstring) }); drop(unsafe { TCString::take_from_arg(tcstring) });
} }
/// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList.
#[no_mangle]
pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) {
debug_assert!(!tcstrings.is_null());
// SAFETY:
// - *tcstrings is a valid TCStringList (caller promises to treat it as read-only)
let strings = unsafe { TCStringList::take_from_arg(tcstrings, TCStringList::null_value()) };
TCStringList::drop_vector(strings);
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test]
fn empty_array_has_non_null_pointer() {
let tcstrings = TCStringList::return_val(Vec::new());
assert!(!tcstrings.items.is_null());
assert_eq!(tcstrings.len, 0);
assert_eq!(tcstrings._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tcstrings = TCStringList::return_val(Vec::new());
// SAFETY: testing expected behavior
unsafe { tc_string_list_free(&mut tcstrings) };
assert!(tcstrings.items.is_null());
assert_eq!(tcstrings.len, 0);
assert_eq!(tcstrings._capacity, 0);
}
const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28"; const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28";
fn make_cstring() -> TCString<'static> { fn make_cstring() -> TCString<'static> {

View file

@ -1,72 +0,0 @@
use crate::traits::*;
use crate::types::*;
use std::ptr::NonNull;
/// TCStringList represents a list of strings.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCStringList {
/// number of strings in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// TCStringList representing each string. these remain owned by the TCStringList instance and will
/// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the
/// *TCStringList at indexes 0..len-1 are not NULL.
items: *const NonNull<TCString<'static>>,
}
impl CArray for TCStringList {
type Element = NonNull<TCString<'static>>;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCStringList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList.
#[no_mangle]
pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) {
debug_assert!(!tcstrings.is_null());
// SAFETY:
// - *tcstrings is a valid TCStringList (caller promises to treat it as read-only)
let strings = unsafe { TCStringList::take_from_arg(tcstrings, TCStringList::null_value()) };
TCStringList::drop_vector(strings);
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_array_has_non_null_pointer() {
let tcstrings = TCStringList::return_val(Vec::new());
assert!(!tcstrings.items.is_null());
assert_eq!(tcstrings.len, 0);
assert_eq!(tcstrings._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tcstrings = TCStringList::return_val(Vec::new());
// SAFETY: testing expected behavior
unsafe { tc_string_list_free(&mut tcstrings) };
assert!(tcstrings.items.is_null());
assert_eq!(tcstrings.len, 0);
assert_eq!(tcstrings._capacity, 0);
}
}

View file

@ -171,6 +171,39 @@ fn to_datetime(time: libc::time_t) -> Option<DateTime<Utc>> {
} }
} }
/// TCTaskList represents a list of tasks.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCTaskList {
/// number of tasks in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// array of pointers representing each task. these remain owned by the TCTaskList instance and
/// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList,
/// and the *TCTaskList at indexes 0..len-1 are not NULL.
items: *const NonNull<TCTask>,
}
impl CArray for TCTaskList {
type Element = NonNull<TCTask>;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCTaskList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// 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.
@ -553,3 +586,39 @@ pub unsafe extern "C" fn tc_task_free<'a>(task: *mut TCTask) {
drop(tctask); drop(tctask);
} }
/// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList.
#[no_mangle]
pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) {
debug_assert!(!tctasks.is_null());
// SAFETY:
// - *tctasks is a valid TCTaskList (caller promises to treat it as read-only)
let tasks = unsafe { TCTaskList::take_from_arg(tctasks, TCTaskList::null_value()) };
TCTaskList::drop_vector(tasks);
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_array_has_non_null_pointer() {
let tctasks = TCTaskList::return_val(Vec::new());
assert!(!tctasks.items.is_null());
assert_eq!(tctasks.len, 0);
assert_eq!(tctasks._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tctasks = TCTaskList::return_val(Vec::new());
// SAFETY: testing expected behavior
unsafe { tc_task_list_free(&mut tctasks) };
assert!(tctasks.items.is_null());
assert_eq!(tctasks.len, 0);
assert_eq!(tctasks._capacity, 0);
}
}

View file

@ -1,72 +0,0 @@
use crate::traits::*;
use crate::types::*;
use std::ptr::NonNull;
/// TCTaskList represents a list of tasks.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCTaskList {
/// number of tasks in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// array of pointers representing each task. these remain owned by the TCTaskList instance and
/// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList,
/// and the *TCTaskList at indexes 0..len-1 are not NULL.
items: *const NonNull<TCTask>,
}
impl CArray for TCTaskList {
type Element = NonNull<TCTask>;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCTaskList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList.
#[no_mangle]
pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) {
debug_assert!(!tctasks.is_null());
// SAFETY:
// - *tctasks is a valid TCTaskList (caller promises to treat it as read-only)
let tasks = unsafe { TCTaskList::take_from_arg(tctasks, TCTaskList::null_value()) };
TCTaskList::drop_vector(tasks);
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_array_has_non_null_pointer() {
let tctasks = TCTaskList::return_val(Vec::new());
assert!(!tctasks.items.is_null());
assert_eq!(tctasks.len, 0);
assert_eq!(tctasks._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tctasks = TCTaskList::return_val(Vec::new());
// SAFETY: testing expected behavior
unsafe { tc_task_list_free(&mut tctasks) };
assert!(tctasks.items.is_null());
assert_eq!(tctasks.len, 0);
assert_eq!(tctasks._capacity, 0);
}
}

View file

@ -3,6 +3,10 @@ use crate::types::*;
use libc; use libc;
use taskchampion::Uuid; use taskchampion::Uuid;
// NOTE: this must be a simple constant so that cbindgen can evaluate it
/// Length, in bytes, of the string representation of a UUID (without NUL terminator)
pub const TC_UUID_STRING_BYTES: usize = 36;
/// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. /// 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. /// Uuids are typically treated as opaque, but the bytes are available in big-endian format.
/// ///
@ -36,9 +40,37 @@ pub unsafe extern "C" fn tc_uuid_nil() -> TCUuid {
TCUuid::return_val(Uuid::nil()) TCUuid::return_val(Uuid::nil())
} }
// NOTE: this must be a simple constant so that cbindgen can evaluate it /// TCUuidList represents a list of uuids.
/// Length, in bytes, of the string representation of a UUID (without NUL terminator) ///
pub const TC_UUID_STRING_BYTES: usize = 36; /// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCUuidList {
/// number of uuids in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// array of uuids. these remain owned by the TCUuidList instance and will be freed by
/// tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList.
items: *const TCUuid,
}
impl CArray for TCUuidList {
type Element = TCUuid;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCUuidList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Write the string representation of a TCUuid into the given buffer, which must be /// Write the string representation of a TCUuid into the given buffer, which must be
/// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. /// at least TC_UUID_STRING_BYTES long. No NUL terminator is added.
@ -91,3 +123,39 @@ pub unsafe extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut T
} }
TCResult::Error TCResult::Error
} }
/// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList.
#[no_mangle]
pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) {
debug_assert!(!tcuuids.is_null());
// SAFETY:
// - *tcuuids is a valid TCUuidList (caller promises to treat it as read-only)
let uuids = unsafe { TCUuidList::take_from_arg(tcuuids, TCUuidList::null_value()) };
TCUuidList::drop_vector(uuids);
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_array_has_non_null_pointer() {
let tcuuids = TCUuidList::return_val(Vec::new());
assert!(!tcuuids.items.is_null());
assert_eq!(tcuuids.len, 0);
assert_eq!(tcuuids._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tcuuids = TCUuidList::return_val(Vec::new());
// SAFETY: testing expected behavior
unsafe { tc_uuid_list_free(&mut tcuuids) };
assert!(tcuuids.items.is_null());
assert_eq!(tcuuids.len, 0);
assert_eq!(tcuuids._capacity, 0);
}
}

View file

@ -1,70 +0,0 @@
use crate::traits::*;
use crate::types::*;
/// TCUuidList represents a list of uuids.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCUuidList {
/// number of uuids in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// array of uuids. these remain owned by the TCUuidList instance and will be freed by
/// tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList.
items: *const TCUuid,
}
impl CArray for TCUuidList {
type Element = TCUuid;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCUuidList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList.
#[no_mangle]
pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) {
debug_assert!(!tcuuids.is_null());
// SAFETY:
// - *tcuuids is a valid TCUuidList (caller promises to treat it as read-only)
let uuids = unsafe { TCUuidList::take_from_arg(tcuuids, TCUuidList::null_value()) };
TCUuidList::drop_vector(uuids);
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_array_has_non_null_pointer() {
let tcuuids = TCUuidList::return_val(Vec::new());
assert!(!tcuuids.items.is_null());
assert_eq!(tcuuids.len, 0);
assert_eq!(tcuuids._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tcuuids = TCUuidList::return_val(Vec::new());
// SAFETY: testing expected behavior
unsafe { tc_uuid_list_free(&mut tcuuids) };
assert!(tcuuids.items.is_null());
assert_eq!(tcuuids.len, 0);
assert_eq!(tcuuids._capacity, 0);
}
}