From 75e10676ceaac8e92d2cfb60a2261cb14cd2d393 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 17 Jan 2023 03:23:43 +0000 Subject: [PATCH] Use ffizz_header to generate taskchampion.h --- Cargo.lock | 140 +- src/tc/rust/Cargo.lock | 87 +- taskchampion/integration-tests/build.rs | 7 +- taskchampion/lib/Cargo.toml | 1 + taskchampion/lib/header-intro.h | 76 - taskchampion/lib/src/annotation.rs | 53 +- taskchampion/lib/src/kv.rs | 37 +- taskchampion/lib/src/lib.rs | 117 ++ taskchampion/lib/src/replica.rs | 108 ++ taskchampion/lib/src/result.rs | 20 +- taskchampion/lib/src/server.rs | 29 + taskchampion/lib/src/status.rs | 41 +- taskchampion/lib/src/string.rs | 87 +- taskchampion/lib/src/task.rs | 436 +++++- taskchampion/lib/src/uda.rs | 45 +- taskchampion/lib/src/uuid.rs | 116 +- taskchampion/lib/src/workingset.rs | 38 + taskchampion/lib/taskchampion.h | 1694 ++++++++++------------- taskchampion/xtask/Cargo.toml | 2 +- taskchampion/xtask/src/main.rs | 28 +- 20 files changed, 1881 insertions(+), 1281 deletions(-) delete mode 100644 taskchampion/lib/header-intro.h diff --git a/Cargo.lock b/Cargo.lock index 3912a8df2..36be34d33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,17 +301,6 @@ version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -408,25 +397,6 @@ dependencies = [ "bytes", ] -[[package]] -name = "cbindgen" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6358dedf60f4d9b8db43ad187391afe959746101346fe51bb978126bec61dfb" -dependencies = [ - "clap 3.2.22", - "heck", - "indexmap", - "log", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn", - "tempfile", - "toml", -] - [[package]] name = "cc" version = "1.0.73" @@ -458,21 +428,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "clap" -version = "3.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" -dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex 0.2.2", - "indexmap", - "strsim", - "termcolor", - "textwrap", -] - [[package]] name = "clap" version = "4.3.0" @@ -491,19 +446,10 @@ dependencies = [ "anstream", "anstyle", "bitflags 1.3.2", - "clap_lex 0.5.0", + "clap_lex", "strsim", ] -[[package]] -name = "clap_lex" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613" -dependencies = [ - "os_str_bytes", -] - [[package]] name = "clap_lex" version = "0.5.0" @@ -606,6 +552,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -670,6 +622,29 @@ dependencies = [ "instant", ] +[[package]] +name = "ffizz-header" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b3ae8dccc2b5edfc7805a0c26cc776ae521fd5f6fdd693520e130abcdce06" +dependencies = [ + "ffizz-macros", + "itertools", + "linkme", +] + +[[package]] +name = "ffizz-macros" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56603703fdb7bcae099f7212b9afb83f0d057236e42d87c894ba6b34ad77ac18" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "flate2" version = "1.0.24" @@ -990,6 +965,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.2" @@ -1049,6 +1033,26 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linkme" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc2b30967da1bcca8f15aa741f2b949a315ef0eabd0ef630a5a0643d7a45260" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a440f823b734f5a90d7cc2850a2254611092e88fa13fb1948556858ce2d35d2a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1167,12 +1171,6 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" -[[package]] -name = "os_str_bytes" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" - [[package]] name = "output_vt100" version = "0.1.3" @@ -1673,6 +1671,7 @@ name = "taskchampion-lib" version = "0.1.0" dependencies = [ "anyhow", + "ffizz-header", "libc", "pretty_assertions", "taskchampion", @@ -1686,7 +1685,7 @@ dependencies = [ "actix-web", "anyhow", "chrono", - "clap 4.3.0", + "clap", "env_logger", "futures", "log", @@ -1722,12 +1721,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" - [[package]] name = "thiserror" version = "1.0.37" @@ -1831,15 +1824,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - [[package]] name = "tracing" version = "0.1.34" @@ -2212,7 +2196,7 @@ name = "xtask" version = "0.4.1" dependencies = [ "anyhow", - "cbindgen", + "taskchampion-lib", ] [[package]] diff --git a/src/tc/rust/Cargo.lock b/src/tc/rust/Cargo.lock index e2532e8f9..3d38c3829 100644 --- a/src/tc/rust/Cargo.lock +++ b/src/tc/rust/Cargo.lock @@ -161,6 +161,12 @@ dependencies = [ "syn", ] +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -173,6 +179,29 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "ffizz-header" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b3ae8dccc2b5edfc7805a0c26cc776ae521fd5f6fdd693520e130abcdce06" +dependencies = [ + "ffizz-macros", + "itertools", + "linkme", +] + +[[package]] +name = "ffizz-macros" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56603703fdb7bcae099f7212b9afb83f0d057236e42d87c894ba6b34ad77ac18" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "flate2" version = "1.0.22" @@ -265,6 +294,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.1" @@ -312,6 +350,26 @@ dependencies = [ "cc", ] +[[package]] +name = "linkme" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22bcb06ef182e7557cf18d85bd151319d657bd8f699d381435781871f3027af8" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f2011c1121c45eb4d9639cf5dcbae9622d2978fc5e922a346bfdc6c46700b5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "log" version = "0.4.17" @@ -376,18 +434,18 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.17" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -525,13 +583,13 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.91" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -559,6 +617,7 @@ name = "taskchampion-lib" version = "0.1.0" dependencies = [ "anyhow", + "ffizz-header", "libc", "taskchampion", ] @@ -630,6 +689,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + [[package]] name = "unicode-normalization" version = "0.1.19" @@ -645,12 +710,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - [[package]] name = "untrusted" version = "0.7.1" diff --git a/taskchampion/integration-tests/build.rs b/taskchampion/integration-tests/build.rs index ed87f40e7..dd10a997c 100644 --- a/taskchampion/integration-tests/build.rs +++ b/taskchampion/integration-tests/build.rs @@ -13,9 +13,11 @@ fn build_bindings_tests(suites: &[&'static str]) { "UNITY_OUTPUT_CHAR_HEADER_DECLARATION", "test_output(char c)", ); - build.file("src/bindings_tests/unity/unity.c"); - let mut files = vec!["src/bindings_tests/test.c".to_string()]; + let mut files = vec![ + "src/bindings_tests/test.c".into(), + "src/bindings_tests/unity/unity.c".into(), + ]; for suite in suites { files.push(format!("src/bindings_tests/{}.c", suite)); } @@ -23,6 +25,7 @@ fn build_bindings_tests(suites: &[&'static str]) { build.file(&file); println!("cargo:rerun-if-changed={}", file); } + println!("cargo:rerun-if-changed=../lib/taskchampion.h"); build.compile("bindings-tests"); } diff --git a/taskchampion/lib/Cargo.toml b/taskchampion/lib/Cargo.toml index 69541aa3c..e2afb3681 100644 --- a/taskchampion/lib/Cargo.toml +++ b/taskchampion/lib/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" libc = "0.2.136" taskchampion = { path = "../taskchampion" } anyhow = "1.0" +ffizz-header = "0.3" [dev-dependencies] pretty_assertions = "1" diff --git a/taskchampion/lib/header-intro.h b/taskchampion/lib/header-intro.h deleted file mode 100644 index c0dd01153..000000000 --- a/taskchampion/lib/header-intro.h +++ /dev/null @@ -1,76 +0,0 @@ -/** - * TaskChampion - * - * This file defines the C interface to libtaskchampion. This is a thin - * wrapper around the Rust `taskchampion` crate. Refer to the documentation - * for that crate at https://docs.rs/taskchampion/latest/taskchampion/ for API - * details. The comments in this file focus mostly on the low-level details of - * passing values to and from TaskChampion. - * - * # Overview - * - * This library defines two major types used to interact with the API, that map directly - * to Rust types. - * - * * TCReplica - see https://docs.rs/taskchampion/latest/taskchampion/struct.Replica.html - * * TCTask - see https://docs.rs/taskchampion/latest/taskchampion/struct.Task.html - * * TCServer - see https://docs.rs/taskchampion/latest/taskchampion/trait.Server.html - * * TCWorkingSet - see https://docs.rs/taskchampion/latest/taskchampion/struct.WorkingSet.html - * - * It also defines a few utility types: - * - * * TCString - a wrapper around both C (NUL-terminated) and Rust (always utf-8) strings. - * * TC…List - a list of objects represented as a C array - * * see below for the remainder - * - * # Safety - * - * Each type contains specific instructions to ensure memory safety. - * The general rules are as follows. - * - * No types in this library are threadsafe. All values should be used in only - * one thread for their entire lifetime. It is safe to use unrelated values in - * different threads (for example, different threads may use different - * TCReplica values concurrently). - * - * ## Pass by Pointer - * - * Several types such as TCReplica and TCString are "opaque" types and always - * handled as pointers in C. The bytes these pointers address are private to - * the Rust implemetation and must not be accessed from C. - * - * Pass-by-pointer values have exactly one owner, and that owner is responsible - * for freeing the value (using a `tc_…_free` function), or transferring - * ownership elsewhere. Except where documented otherwise, when a value is - * passed to C, ownership passes to C as well. When a value is passed to Rust, - * ownership stays with the C code. The exception is TCString, ownership of - * which passes to Rust when it is used as a function argument. - * - * The limited circumstances where one value must not outlive another, due to - * pointer references between them, are documented below. - * - * ## Pass by Value - * - * Types such as TCUuid and TC…List are passed by value, and contain fields - * that are accessible from C. C code is free to access the content of these - * types in a _read_only_ fashion. - * - * Pass-by-value values that contain pointers also have exactly one owner, - * responsible for freeing the value or transferring ownership. The tc_…_free - * functions for these types will replace the pointers with NULL to guard - * against use-after-free errors. The interior pointers in such values should - * never be freed directly (for example, `tc_string_free(tcuda.value)` is an - * error). - * - * TCUuid is a special case, because it does not contain pointers. It can be - * freely copied and need not be freed. - * - * ## Lists - * - * Lists are a special kind of pass-by-value type. Each contains `len` and - * `items`, where `items` is an array of length `len`. Lists, and the values - * in the `items` array, must be treated as read-only. On return from an API - * function, a list's ownership is with the C caller, which must eventually - * free the list. List data must be freed with the `tc_…_list_free` function. - * It is an error to free any value in the `items` array of a list. - */ diff --git a/taskchampion/lib/src/annotation.rs b/taskchampion/lib/src/annotation.rs index 5b28caf9d..9c709bc49 100644 --- a/taskchampion/lib/src/annotation.rs +++ b/taskchampion/lib/src/annotation.rs @@ -2,6 +2,10 @@ use crate::traits::*; use crate::types::*; use taskchampion::chrono::prelude::*; +#[ffizz_header::item] +#[ffizz(order = 400)] +/// ***** TCAnnotation ***** +/// /// TCAnnotation contains the details of an annotation. /// /// # Safety @@ -17,6 +21,15 @@ use taskchampion::chrono::prelude::*; /// after the call returns. In fact, the value will be zeroed out to ensure this. /// /// TCAnnotations are not threadsafe. +/// +/// ```c +/// typedef struct TCAnnotation { +/// // Time the annotation was made. Must be nonzero. +/// time_t entry; +/// // Content of the annotation. Must not be NULL. +/// TCString description; +/// } TCAnnotation; +/// ``` #[repr(C)] pub struct TCAnnotation { /// Time the annotation was made. Must be nonzero. @@ -62,18 +75,34 @@ impl Default for TCAnnotation { } } +#[ffizz_header::item] +#[ffizz(order = 410)] +/// ***** TCAnnotationList ***** +/// /// TCAnnotationList represents a list of annotations. /// /// The content of this struct must be treated as read-only. +/// +/// ```c +/// typedef struct TCAnnotationList { +/// // number of annotations in items +/// size_t len; +/// // reserved +/// size_t _u1; +/// // Array of annotations. These remain owned by the TCAnnotationList instance and will be freed by +/// // tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList. +/// struct TCAnnotation *items; +/// } TCAnnotationList; +/// ``` #[repr(C)] pub struct TCAnnotationList { /// number of annotations in items len: libc::size_t, /// total size of items (internal use only) - _capacity: libc::size_t, + capacity: libc::size_t, - /// array of annotations. these remain owned by the TCAnnotationList instance and will be freed by + /// Array of annotations. These remain owned by the TCAnnotationList instance and will be freed by /// tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList. items: *mut TCAnnotation, } @@ -84,7 +113,7 @@ impl CList for TCAnnotationList { unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { TCAnnotationList { len, - _capacity: cap, + capacity: cap, items, } } @@ -99,12 +128,18 @@ impl CList for TCAnnotationList { } fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self._capacity) + (self.items, self.len, self.capacity) } } +#[ffizz_header::item] +#[ffizz(order = 401)] /// Free a TCAnnotation instance. The instance, and the TCString it contains, must not be used /// after this call. +/// +/// ```c +/// EXTERN_C void tc_annotation_free(struct TCAnnotation *tcann); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_annotation_free(tcann: *mut TCAnnotation) { debug_assert!(!tcann.is_null()); @@ -115,10 +150,16 @@ pub unsafe extern "C" fn tc_annotation_free(tcann: *mut TCAnnotation) { drop(annotation); } +#[ffizz_header::item] +#[ffizz(order = 411)] /// Free a TCAnnotationList instance. The instance, and all TCAnnotations it contains, must not be used after /// this call. /// /// When this call returns, the `items` pointer will be NULL, signalling an invalid TCAnnotationList. +/// +/// ```c +/// EXTERN_C void tc_annotation_list_free(struct TCAnnotationList *tcanns); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_annotation_list_free(tcanns: *mut TCAnnotationList) { // SAFETY: @@ -137,7 +178,7 @@ mod test { let tcanns = unsafe { TCAnnotationList::return_val(Vec::new()) }; assert!(!tcanns.items.is_null()); assert_eq!(tcanns.len, 0); - assert_eq!(tcanns._capacity, 0); + assert_eq!(tcanns.capacity, 0); } #[test] @@ -147,6 +188,6 @@ mod test { unsafe { tc_annotation_list_free(&mut tcanns) }; assert!(tcanns.items.is_null()); assert_eq!(tcanns.len, 0); - assert_eq!(tcanns._capacity, 0); + assert_eq!(tcanns.capacity, 0); } } diff --git a/taskchampion/lib/src/kv.rs b/taskchampion/lib/src/kv.rs index 8fcbaabbe..c30119688 100644 --- a/taskchampion/lib/src/kv.rs +++ b/taskchampion/lib/src/kv.rs @@ -1,10 +1,21 @@ use crate::traits::*; use crate::types::*; +#[ffizz_header::item] +#[ffizz(order = 600)] +/// ***** TCKV ***** +/// /// TCKV contains a key/value pair that is part of a task. /// /// Neither key nor value are ever NULL. They remain owned by the TCKV and /// will be freed when it is freed with tc_kv_list_free. +/// +/// ```c +/// typedef struct TCKV { +/// struct TCString key; +/// struct TCString value; +/// } TCKV; +/// ``` #[repr(C)] pub struct TCKV { pub key: TCString, @@ -37,9 +48,25 @@ impl PassByValue for TCKV { } } +#[ffizz_header::item] +#[ffizz(order = 610)] +/// ***** TCKVList ***** +/// /// TCKVList represents a list of key/value pairs. /// /// The content of this struct must be treated as read-only. +/// +/// ```c +/// typedef struct TCKVList { +/// // number of key/value pairs in items +/// size_t len; +/// // reserved +/// size_t _u1; +/// // Array of TCKV's. These remain owned by the TCKVList instance and will be freed by +/// // tc_kv_list_free. This pointer is never NULL for a valid TCKVList. +/// struct TCKV *items; +/// } TCKVList; +/// ``` #[repr(C)] pub struct TCKVList { /// number of key/value pairs in items @@ -48,7 +75,7 @@ pub struct TCKVList { /// total size of items (internal use only) _capacity: libc::size_t, - /// array of TCKV's. these remain owned by the TCKVList instance and will be freed by + /// Array of TCKV's. These remain owned by the TCKVList instance and will be freed by /// tc_kv_list_free. This pointer is never NULL for a valid TCKVList. items: *mut TCKV, } @@ -78,10 +105,18 @@ impl CList for TCKVList { } } +// NOTE: callers never have a TCKV that is not in a list, so there is no tc_kv_free. + +#[ffizz_header::item] +#[ffizz(order = 611)] /// Free a TCKVList instance. The instance, and all TCKVs it contains, must not be used after /// this call. /// /// When this call returns, the `items` pointer will be NULL, signalling an invalid TCKVList. +/// +/// ```c +/// EXTERN_C void tc_kv_list_free(struct TCKVList *tckvs); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_kv_list_free(tckvs: *mut TCKVList) { // SAFETY: diff --git a/taskchampion/lib/src/lib.rs b/taskchampion/lib/src/lib.rs index ee48ce969..5bd7b9fdf 100644 --- a/taskchampion/lib/src/lib.rs +++ b/taskchampion/lib/src/lib.rs @@ -12,6 +12,117 @@ #![deny(clippy::extra_unused_lifetimes)] #![deny(clippy::unnecessary_to_owned)] +// ffizz_header orders: +// +// 000-099: header matter +// 100-199: TCResult +// 200-299: TCString / List +// 300-399: TCUuid / List +// 400-499: TCAnnotation / List +// 500-599: TCUda / List +// 600-699: TCKV / List +// 700-799: TCStatus +// 800-899: TCServer +// 900-999: TCReplica +// 1000-1099: TCTask / List +// 1100-1199: TCWorkingSet +// 10000-10099: footer + +ffizz_header::snippet! { +#[ffizz(name="intro", order=0)] +/// TaskChampion +/// +/// This file defines the C interface to libtaskchampion. This is a thin wrapper around the Rust +/// `taskchampion` crate. Refer to the documentation for that crate at +/// https://docs.rs/taskchampion/latest/taskchampion/ for API details. The comments in this file +/// focus mostly on the low-level details of passing values to and from TaskChampion. +/// +/// # Overview +/// +/// This library defines two major types used to interact with the API, that map directly to Rust +/// types. +/// +/// * TCReplica - see https://docs.rs/taskchampion/latest/taskchampion/struct.Replica.html * TCTask +/// - see https://docs.rs/taskchampion/latest/taskchampion/struct.Task.html * TCServer - see +/// https://docs.rs/taskchampion/latest/taskchampion/trait.Server.html * TCWorkingSet - see +/// https://docs.rs/taskchampion/latest/taskchampion/struct.WorkingSet.html +/// +/// It also defines a few utility types: +/// +/// * TCString - a wrapper around both C (NUL-terminated) and Rust (always utf-8) strings. * +/// TC…List - a list of objects represented as a C array * see below for the remainder +/// +/// # Safety +/// +/// Each type contains specific instructions to ensure memory safety. The general rules are as +/// follows. +/// +/// No types in this library are threadsafe. All values should be used in only one thread for their +/// entire lifetime. It is safe to use unrelated values in different threads (for example, +/// different threads may use different TCReplica values concurrently). +/// +/// ## Pass by Pointer +/// +/// Several types such as TCReplica and TCString are "opaque" types and always handled as pointers +/// in C. The bytes these pointers address are private to the Rust implemetation and must not be +/// accessed from C. +/// +/// Pass-by-pointer values have exactly one owner, and that owner is responsible for freeing the +/// value (using a `tc_…_free` function), or transferring ownership elsewhere. Except where +/// documented otherwise, when a value is passed to C, ownership passes to C as well. When a value +/// is passed to Rust, ownership stays with the C code. The exception is TCString, ownership of +/// which passes to Rust when it is used as a function argument. +/// +/// The limited circumstances where one value must not outlive another, due to pointer references +/// between them, are documented below. +/// +/// ## Pass by Value +/// +/// Types such as TCUuid and TC…List are passed by value, and contain fields that are accessible +/// from C. C code is free to access the content of these types in a _read_only_ fashion. +/// +/// Pass-by-value values that contain pointers also have exactly one owner, responsible for freeing +/// the value or transferring ownership. The tc_…_free functions for these types will replace the +/// pointers with NULL to guard against use-after-free errors. The interior pointers in such values +/// should never be freed directly (for example, `tc_string_free(tcuda.value)` is an error). +/// +/// TCUuid is a special case, because it does not contain pointers. It can be freely copied and +/// need not be freed. +/// +/// ## Lists +/// +/// Lists are a special kind of pass-by-value type. Each contains `len` and `items`, where `items` +/// is an array of length `len`. Lists, and the values in the `items` array, must be treated as +/// read-only. On return from an API function, a list's ownership is with the C caller, which must +/// eventually free the list. List data must be freed with the `tc_…_list_free` function. It is an +/// error to free any value in the `items` array of a list. +} + +ffizz_header::snippet! { +#[ffizz(name="topmatter", order=1)] +/// ```c +/// #ifndef TASKCHAMPION_H +/// #define TASKCHAMPION_H +/// +/// #include +/// #include +/// #include +/// +/// #ifdef __cplusplus +/// #define EXTERN_C extern "C" +/// #else +/// #define EXTERN_C +/// #endif // __cplusplus +/// ``` +} + +ffizz_header::snippet! { +#[ffizz(name="bottomatter", order=10000)] +/// ```c +/// #endif /* TASKCHAMPION_H */ +/// ``` +} + mod traits; mod util; @@ -53,3 +164,9 @@ pub(crate) mod types { pub(crate) use crate::uuid::{TCUuid, TCUuidList}; pub(crate) use crate::workingset::TCWorkingSet; } + +#[cfg(debug_assertions)] +/// Generate the taskchapion.h header +pub fn generate_header() -> String { + ffizz_header::generate() +} diff --git a/taskchampion/lib/src/replica.rs b/taskchampion/lib/src/replica.rs index d6a805183..390d45179 100644 --- a/taskchampion/lib/src/replica.rs +++ b/taskchampion/lib/src/replica.rs @@ -4,6 +4,10 @@ use crate::util::err_to_ruststring; use std::ptr::NonNull; use taskchampion::{Replica, StorageConfig}; +#[ffizz_header::item] +#[ffizz(order = 900)] +/// ***** TCReplica ***** +/// /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. /// @@ -26,6 +30,10 @@ use taskchampion::{Replica, StorageConfig}; /// Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again. /// /// TCReplicas are not threadsafe. +/// +/// ```c +/// typedef struct TCReplica TCReplica; +/// ``` pub struct TCReplica { /// The wrapped Replica inner: Replica, @@ -122,8 +130,14 @@ where } } +#[ffizz_header::item] +#[ffizz(order = 901)] /// Create a new TCReplica with an in-memory database. The contents of the database will be /// lost when it is freed with tc_replica_free. +/// +/// ```c +/// EXTERN_C struct TCReplica *tc_replica_new_in_memory(void); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { let storage = StorageConfig::InMemory @@ -134,9 +148,17 @@ pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { unsafe { TCReplica::from(Replica::new(storage)).return_ptr() } } +#[ffizz_header::item] +#[ffizz(order = 901)] /// Create a new TCReplica with an on-disk database having the given filename. On error, a string /// is written to the error_out parameter (if it is not NULL) and NULL is returned. The caller /// must free this string. +/// +/// ```c +/// EXTERN_C struct TCReplica *tc_replica_new_on_disk(struct TCString path, +/// bool create_if_missing, +/// struct TCString *error_out); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_new_on_disk( path: TCString, @@ -164,9 +186,15 @@ pub unsafe extern "C" fn tc_replica_new_on_disk( ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Get a list of all tasks in the replica. /// /// Returns a TCTaskList with a NULL items field on error. +/// +/// ```c +/// EXTERN_C struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList { wrap( @@ -197,11 +225,17 @@ pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Get a list of all uuids for tasks in the replica. /// /// Returns a TCUuidList with a NULL items field on error. /// /// The caller must free the UUID list with `tc_uuid_list_free`. +/// +/// ```c +/// EXTERN_C struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUuidList { wrap( @@ -222,10 +256,16 @@ pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUui ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Get the current working set for this replica. The resulting value must be freed /// with tc_working_set_free. /// /// Returns NULL on error. +/// +/// ```c +/// EXTERN_C struct TCWorkingSet *tc_replica_working_set(struct TCReplica *rep); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_working_set(rep: *mut TCReplica) -> *mut TCWorkingSet { wrap( @@ -240,10 +280,16 @@ pub unsafe extern "C" fn tc_replica_working_set(rep: *mut TCReplica) -> *mut TCW ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Get an existing task by its UUID. /// /// Returns NULL when the task does not exist, and on error. Consult tc_replica_error /// to distinguish the two conditions. +/// +/// ```c +/// EXTERN_C struct TCTask *tc_replica_get_task(struct TCReplica *rep, struct TCUuid tcuuid); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *mut TCTask { wrap( @@ -265,9 +311,17 @@ pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Create a new task. The task must not already exist. /// /// Returns the task, or NULL on error. +/// +/// ```c +/// EXTERN_C struct TCTask *tc_replica_new_task(struct TCReplica *rep, +/// enum TCStatus status, +/// struct TCString description); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_new_task( rep: *mut TCReplica, @@ -290,9 +344,15 @@ pub unsafe extern "C" fn tc_replica_new_task( ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Create a new task. The task must not already exist. /// /// Returns the task, or NULL on error. +/// +/// ```c +/// EXTERN_C struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_import_task_with_uuid( rep: *mut TCReplica, @@ -314,9 +374,15 @@ pub unsafe extern "C" fn tc_replica_import_task_with_uuid( ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Synchronize this replica with a server. /// /// The `server` argument remains owned by the caller, and must be freed explicitly. +/// +/// ```c +/// EXTERN_C TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_sync( rep: *mut TCReplica, @@ -340,10 +406,16 @@ pub unsafe extern "C" fn tc_replica_sync( ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Undo local operations until the most recent UndoPoint. /// /// If undone_out is not NULL, then on success it is set to 1 if operations were undone, or 0 if /// there are no operations that can be done. +/// +/// ```c +/// EXTERN_C TCResult tc_replica_undo(struct TCReplica *rep, int32_t *undone_out); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_undo(rep: *mut TCReplica, undone_out: *mut i32) -> TCResult { wrap( @@ -362,8 +434,14 @@ pub unsafe extern "C" fn tc_replica_undo(rep: *mut TCReplica, undone_out: *mut i ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Get the number of local, un-synchronized operations (not including undo points), or -1 on /// error. +/// +/// ```c +/// EXTERN_C int64_t tc_replica_num_local_operations(struct TCReplica *rep); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_num_local_operations(rep: *mut TCReplica) -> i64 { wrap( @@ -376,7 +454,13 @@ pub unsafe extern "C" fn tc_replica_num_local_operations(rep: *mut TCReplica) -> ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Get the number of undo points (number of undo calls possible), or -1 on error. +/// +/// ```c +/// EXTERN_C int64_t tc_replica_num_undo_points(struct TCReplica *rep); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_num_undo_points(rep: *mut TCReplica) -> i64 { wrap( @@ -389,10 +473,16 @@ pub unsafe extern "C" fn tc_replica_num_undo_points(rep: *mut TCReplica) -> i64 ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Add an UndoPoint, if one has not already been added by this Replica. This occurs automatically /// when a change is made. The `force` flag allows forcing a new UndoPoint even if one has already /// been created by this Replica, and may be useful when a Replica instance is held for a long time /// and used to apply more than one user-visible change. +/// +/// ```c +/// EXTERN_C TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_add_undo_point(rep: *mut TCReplica, force: bool) -> TCResult { wrap( @@ -405,9 +495,15 @@ pub unsafe extern "C" fn tc_replica_add_undo_point(rep: *mut TCReplica, force: b ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Rebuild this replica's working set, based on whether tasks are pending or not. If `renumber` /// is true, then existing tasks may be moved to new working-set indices; in any case, on /// completion all pending tasks are in the working set and all non- pending tasks are not. +/// +/// ```c +/// EXTERN_C TCResult tc_replica_rebuild_working_set(struct TCReplica *rep, bool renumber); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_rebuild_working_set( rep: *mut TCReplica, @@ -423,9 +519,15 @@ pub unsafe extern "C" fn tc_replica_rebuild_working_set( ) } +#[ffizz_header::item] +#[ffizz(order = 902)] /// Get the latest error for a replica, or a string with NULL ptr if no error exists. Subsequent /// calls to this function will return NULL. The rep pointer must not be NULL. The caller must /// free the returned string. +/// +/// ```c +/// EXTERN_C struct TCString tc_replica_error(struct TCReplica *rep); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> TCString { // SAFETY: @@ -443,8 +545,14 @@ pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> TCString { } } +#[ffizz_header::item] +#[ffizz(order = 903)] /// Free a replica. The replica may not be used after this function returns and must not be freed /// more than once. +/// +/// ```c +/// EXTERN_C void tc_replica_free(struct TCReplica *rep); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_replica_free(rep: *mut TCReplica) { // SAFETY: diff --git a/taskchampion/lib/src/result.rs b/taskchampion/lib/src/result.rs index a7d53ea8d..bb72efb1d 100644 --- a/taskchampion/lib/src/result.rs +++ b/taskchampion/lib/src/result.rs @@ -1,7 +1,23 @@ +#[ffizz_header::item] +#[ffizz(order = 100)] +/// ***** TCResult ***** +/// /// A result from a TC operation. Typically if this value is TC_RESULT_ERROR, /// the associated object's `tc_.._error` method will return an error message. -/// cbindgen:prefix-with-name -/// cbindgen:rename-all=ScreamingSnakeCase +/// +/// ```c +/// enum TCResult +/// #ifdef __cplusplus +/// : int32_t +/// #endif // __cplusplus +/// { +/// TC_RESULT_ERROR = -1, +/// TC_RESULT_OK = 0, +/// }; +/// #ifndef __cplusplus +/// typedef int32_t TCResult; +/// #endif // __cplusplus +/// ``` #[repr(i32)] pub enum TCResult { Error = -1, diff --git a/taskchampion/lib/src/server.rs b/taskchampion/lib/src/server.rs index dc1160d0c..7c923a236 100644 --- a/taskchampion/lib/src/server.rs +++ b/taskchampion/lib/src/server.rs @@ -3,12 +3,20 @@ use crate::types::*; use crate::util::err_to_ruststring; use taskchampion::{Server, ServerConfig}; +#[ffizz_header::item] +#[ffizz(order = 800)] +/// ***** TCServer ***** +/// /// TCServer represents an interface to a sync server. Aside from new and free, a server /// has no C-accessible API, but is designed to be passed to `tc_replica_sync`. /// /// ## Safety /// /// TCServer are not threadsafe, and must not be used with multiple replicas simultaneously. +/// +/// ```c +/// typedef struct TCServer TCServer; +/// ``` pub struct TCServer(Box); impl PassByPointer for TCServer {} @@ -53,6 +61,8 @@ where } } +#[ffizz_header::item] +#[ffizz(order = 801)] /// Create a new TCServer that operates locally (on-disk). See the TaskChampion docs for the /// description of the arguments. /// @@ -60,6 +70,10 @@ where /// returned. The caller must free this string. /// /// The server must be freed after it is used - tc_replica_sync does not automatically free it. +/// +/// ```c +/// EXTERN_C struct TCServer *tc_server_new_local(struct TCString server_dir, struct TCString *error_out); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_server_new_local( server_dir: TCString, @@ -83,6 +97,8 @@ pub unsafe extern "C" fn tc_server_new_local( ) } +#[ffizz_header::item] +#[ffizz(order = 801)] /// Create a new TCServer that connects to a remote server. See the TaskChampion docs for the /// description of the arguments. /// @@ -90,6 +106,13 @@ pub unsafe extern "C" fn tc_server_new_local( /// returned. The caller must free this string. /// /// The server must be freed after it is used - tc_replica_sync does not automatically free it. +/// +/// ```c +/// EXTERN_C struct TCServer *tc_server_new_remote(struct TCString origin, +/// struct TCUuid client_key, +/// struct TCString encryption_secret, +/// struct TCString *error_out); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_server_new_remote( origin: TCString, @@ -129,8 +152,14 @@ pub unsafe extern "C" fn tc_server_new_remote( ) } +#[ffizz_header::item] +#[ffizz(order = 802)] /// Free a server. The server may not be used after this function returns and must not be freed /// more than once. +/// +/// ```c +/// EXTERN_C void tc_server_free(struct TCServer *server); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_server_free(server: *mut TCServer) { debug_assert!(!server.is_null()); diff --git a/taskchampion/lib/src/status.rs b/taskchampion/lib/src/status.rs index 20a7e84b7..e0d370136 100644 --- a/taskchampion/lib/src/status.rs +++ b/taskchampion/lib/src/status.rs @@ -1,17 +1,38 @@ pub use taskchampion::Status; +#[ffizz_header::item] +#[ffizz(order = 700)] +/// ***** TCStatus ***** +/// /// The status of a task, as defined by the task data model. -/// cbindgen:prefix-with-name -/// cbindgen:rename-all=ScreamingSnakeCase -#[repr(C)] +/// +/// ```c +/// #ifdef __cplusplus +/// typedef enum TCStatus : int32_t { +/// #else // __cplusplus +/// typedef int32_t TCStatus; +/// enum TCStatus { +/// #endif // __cplusplus +/// TC_STATUS_PENDING = 0, +/// TC_STATUS_COMPLETED = 1, +/// TC_STATUS_DELETED = 2, +/// TC_STATUS_RECURRING = 3, +/// // Unknown signifies a status in the task DB that was not +/// // recognized. +/// TC_STATUS_UNKNOWN = -1, +/// #ifdef __cplusplus +/// } TCStatus; +/// #else // __cplusplus +/// }; +/// #endif // __cplusplus +/// ``` +#[repr(i32)] pub enum TCStatus { - Pending, - Completed, - Deleted, - Recurring, - /// Unknown signifies a status in the task DB that was not - /// recognized. - Unknown, + Pending = 0, + Completed = 1, + Deleted = 2, + Recurring = 3, + Unknown = -1, } impl From for Status { diff --git a/taskchampion/lib/src/string.rs b/taskchampion/lib/src/string.rs index 44e00dcc8..aa92bda0a 100644 --- a/taskchampion/lib/src/string.rs +++ b/taskchampion/lib/src/string.rs @@ -4,6 +4,10 @@ use std::ffi::{CStr, CString, OsString}; use std::os::raw::c_char; use std::path::PathBuf; +#[ffizz_header::item] +#[ffizz(order = 200)] +/// ***** TCString ***** +/// /// TCString supports passing strings into and out of the TaskChampion API. /// /// # Rust Strings and C Strings @@ -41,7 +45,15 @@ use std::path::PathBuf; /// for such a value. /// /// TCString is not threadsafe. -/// cbindgen:field-names=[ptr, _u1, _u2, _u3] +/// +/// ```c +/// typedef struct TCString { +/// void *ptr; // opaque, but may be checked for NULL +/// size_t _u1; // reserved +/// size_t _u2; // reserved +/// uint8_t _u3; // reserved +/// } TCString; +/// ``` #[repr(C)] pub struct TCString { // defined based on the type @@ -360,19 +372,36 @@ where rv } +#[ffizz_header::item] +#[ffizz(order = 210)] +/// ***** TCStringList ***** +/// /// TCStringList represents a list of strings. /// /// The content of this struct must be treated as read-only. +/// +/// ```c +/// typedef struct TCStringList { +/// // number of strings in items +/// size_t len; +/// // reserved +/// size_t _u1; +/// // 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. +/// struct TCString *items; +/// } TCStringList; +/// ``` #[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, + 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 + /// Array of strings. 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: *mut TCString, } @@ -383,7 +412,7 @@ impl CList for TCStringList { unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { TCStringList { len, - _capacity: cap, + capacity: cap, items, } } @@ -398,10 +427,12 @@ impl CList for TCStringList { } fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self._capacity) + (self.items, self.len, self.capacity) } } +#[ffizz_header::item] +#[ffizz(order = 201)] /// 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 /// static string. @@ -416,6 +447,10 @@ impl CList for TCStringList { /// tc_task_annotate(task, tc_string_borrow(url)); // TCString created, passed, and freed /// free(url); // string is no longer referenced and can be freed /// ``` +/// +/// ```c +/// EXTERN_C struct TCString tc_string_borrow(const char *cstr); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> TCString { debug_assert!(!cstr.is_null()); @@ -430,8 +465,14 @@ pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> TCString unsafe { TCString::return_val(RustString::CStr(cstr)) } } +#[ffizz_header::item] +#[ffizz(order = 201)] /// Create a new TCString by cloning the content of the given C string. The resulting TCString /// is independent of the given string, which can be freed or overwritten immediately. +/// +/// ```c +/// EXTERN_C struct TCString tc_string_clone(const char *cstr); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> TCString { debug_assert!(!cstr.is_null()); @@ -447,6 +488,8 @@ pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> TCString unsafe { TCString::return_val(RustString::CString(cstring)) } } +#[ffizz_header::item] +#[ffizz(order = 201)] /// Create a new TCString containing the given string with the given length. This allows creation /// of strings containing embedded NUL characters. As with `tc_string_clone`, the resulting /// TCString is independent of the passed buffer, which may be reused or freed immediately. @@ -454,6 +497,10 @@ pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> TCString /// The length should _not_ include any trailing NUL. /// /// The given length must be less than half the maximum value of usize. +/// +/// ```c +/// EXTERN_C struct TCString tc_string_clone_with_len(const char *buf, size_t len); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_string_clone_with_len( buf: *const libc::c_char, @@ -477,6 +524,8 @@ pub unsafe extern "C" fn tc_string_clone_with_len( unsafe { TCString::return_val(RustString::Bytes(vec)) } } +#[ffizz_header::item] +#[ffizz(order = 201)] /// Get the content of the string as a regular C string. The given string must be valid. The /// returned value is NULL if the string contains NUL bytes or (in some cases) invalid UTF-8. The /// returned C string is valid until the TCString is freed or passed to another TC API function. @@ -488,6 +537,10 @@ pub unsafe extern "C" fn tc_string_clone_with_len( /// terminator. The pointer must not be NULL. /// /// This function does _not_ take ownership of the TCString. +/// +/// ```c +/// EXTERN_C const char *tc_string_content(const struct TCString *tcstring); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_string_content(tcstring: *const TCString) -> *const libc::c_char { // SAFETY; @@ -514,6 +567,8 @@ pub unsafe extern "C" fn tc_string_content(tcstring: *const TCString) -> *const } } +#[ffizz_header::item] +#[ffizz(order = 201)] /// Get the content of the string as a pointer and length. The given string must not be NULL. /// This function can return any string, even one including NUL bytes or invalid UTF-8. The /// returned buffer is valid until the TCString is freed or passed to another TaskChampio @@ -523,6 +578,10 @@ pub unsafe extern "C" fn tc_string_content(tcstring: *const TCString) -> *const /// terminator. The pointer must not be NULL. /// /// This function does _not_ take ownership of the TCString. +/// +/// ```c +/// EXTERN_C const char *tc_string_content_with_len(const struct TCString *tcstring, size_t *len_out); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_string_content_with_len( tcstring: *const TCString, @@ -546,8 +605,14 @@ pub unsafe extern "C" fn tc_string_content_with_len( } } +#[ffizz_header::item] +#[ffizz(order = 201)] /// Free a TCString. The given string must not be NULL. The string must not be used /// after this function returns, and must not be freed more than once. +/// +/// ```c +/// EXTERN_C void tc_string_free(struct TCString *tcstring); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) { // SAFETY: @@ -556,10 +621,16 @@ pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) { drop(unsafe { TCString::take_val_from_arg(tcstring, TCString::default()) }); } +#[ffizz_header::item] +#[ffizz(order = 211)] /// 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. +/// +/// ```c +/// EXTERN_C void tc_string_list_free(struct TCStringList *tcstrings); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { // SAFETY: @@ -579,7 +650,7 @@ mod test { let tcstrings = unsafe { TCStringList::return_val(Vec::new()) }; assert!(!tcstrings.items.is_null()); assert_eq!(tcstrings.len, 0); - assert_eq!(tcstrings._capacity, 0); + assert_eq!(tcstrings.capacity, 0); } #[test] @@ -589,7 +660,7 @@ mod test { unsafe { tc_string_list_free(&mut tcstrings) }; assert!(tcstrings.items.is_null()); assert_eq!(tcstrings.len, 0); - assert_eq!(tcstrings._capacity, 0); + assert_eq!(tcstrings.capacity, 0); } const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28"; diff --git a/taskchampion/lib/src/task.rs b/taskchampion/lib/src/task.rs index f734c05ac..eb729c573 100644 --- a/taskchampion/lib/src/task.rs +++ b/taskchampion/lib/src/task.rs @@ -7,6 +7,10 @@ use std::ptr::NonNull; use std::str::FromStr; use taskchampion::{utc_timestamp, Annotation, Tag, Task, TaskMut, Uuid}; +#[ffizz_header::item] +#[ffizz(order = 1000)] +/// ***** TCTask ***** +/// /// A task, as publicly exposed by this library. /// /// A task begins in "immutable" mode. It must be converted to "mutable" mode @@ -36,6 +40,10 @@ use taskchampion::{utc_timestamp, Annotation, Tag, Task, TaskMut, Uuid}; /// Once passed to tc_task_free, a `*TCTask` becomes invalid and must not be used again. /// /// TCTasks are not threadsafe. +/// +/// ```c +/// typedef struct TCTask TCTask; +/// ``` pub struct TCTask { /// The wrapped Task or TaskMut inner: Inner, @@ -168,23 +176,40 @@ impl TryFrom> for Tag { } } +#[ffizz_header::item] +#[ffizz(order = 1010)] +/// ***** TCTaskList ***** +/// /// TCTaskList represents a list of tasks. /// /// The content of this struct must be treated as read-only: no fields or anything they reference /// should be modified directly by C code. /// /// When an item is taken from this list, its pointer in `items` is set to NULL. +/// +/// ```c +/// typedef struct TCTaskList { +/// // number of tasks in items +/// size_t len; +/// // reserved +/// size_t _u1; +/// // 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. +/// // Pointers in the array may be NULL after `tc_task_list_take`. +/// struct TCTask **items; +/// } TCTaskList; +/// ``` #[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, + 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. + /// 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. + /// Pointers in the array may be NULL after `tc_task_list_take`. items: *mut Option>, } @@ -194,7 +219,7 @@ impl CList for TCTaskList { unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { TCTaskList { len, - _capacity: cap, + capacity: cap, items, } } @@ -209,55 +234,18 @@ impl CList for TCTaskList { } fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self._capacity) + (self.items, self.len, self.capacity) } } -/// Convert an immutable task into a mutable task. -/// -/// The task must not be NULL. It is modified in-place, and becomes mutable. -/// -/// The replica must not be NULL. After this function returns, the replica _cannot be used at all_ -/// until this task is made immutable again. This implies that it is not allowed for more than one -/// task associated with a replica to be mutable at any time. -/// -/// Typical mutation of tasks is bracketed with `tc_task_to_mut` and `tc_task_to_immut`: +#[ffizz_header::item] +#[ffizz(order = 1001)] +/// Get a task's UUID. /// /// ```c -/// tc_task_to_mut(task, rep); -/// success = tc_task_done(task); -/// tc_task_to_immut(task, rep); -/// if (!success) { ... } +/// EXTERN_C struct TCUuid tc_task_get_uuid(struct TCTask *task); /// ``` #[no_mangle] -pub unsafe extern "C" fn tc_task_to_mut(task: *mut TCTask, tcreplica: *mut TCReplica) { - // SAFETY: - // - task is not null (promised by caller) - // - task outlives 'a (promised by caller) - let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - // SAFETY: - // - tcreplica is not NULL (promised by caller) - // - tcreplica lives until later call to to_immut via tc_task_to_immut (promised by caller, - // who cannot call tc_replica_free during this time) - unsafe { tctask.to_mut(tcreplica) }; -} - -/// Convert a mutable task into an immutable task. -/// -/// The task must not be NULL. It is modified in-place, and becomes immutable. -/// -/// The replica passed to `tc_task_to_mut` may be used freely after this call. -#[no_mangle] -pub unsafe extern "C" fn tc_task_to_immut(task: *mut TCTask) { - // SAFETY: - // - task is not null (promised by caller) - // - task outlives 'a (promised by caller) - let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - tctask.to_immut(); -} - -/// Get a task's UUID. -#[no_mangle] pub unsafe extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { wrap(task, |task| { // SAFETY: @@ -266,15 +254,27 @@ pub unsafe extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { }) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get a task's status. +/// +/// ```c +/// EXTERN_C enum TCStatus tc_task_get_status(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_status(task: *mut TCTask) -> TCStatus { wrap(task, |task| task.get_status().into()) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get the underlying key/value pairs for this task. The returned TCKVList is /// a "snapshot" of the task and will not be updated if the task is subsequently /// modified. It is the caller's responsibility to free the TCKVList. +/// +/// ```c +/// EXTERN_C struct TCKVList tc_task_get_taskmap(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList { wrap(task, |task| { @@ -293,7 +293,13 @@ pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList { }) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get a task's description. +/// +/// ```c +/// EXTERN_C struct TCString tc_task_get_description(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_description(task: *mut TCTask) -> TCString { wrap(task, |task| { @@ -304,8 +310,14 @@ pub unsafe extern "C" fn tc_task_get_description(task: *mut TCTask) -> TCString }) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get a task property's value, or NULL if the task has no such property, (including if the /// property name is not valid utf-8). +/// +/// ```c +/// EXTERN_C struct TCString tc_task_get_value(struct TCTask *task, struct TCString property); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_value(task: *mut TCTask, property: TCString) -> TCString { // SAFETY: @@ -325,50 +337,98 @@ pub unsafe extern "C" fn tc_task_get_value(task: *mut TCTask, property: TCString }) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get the entry timestamp for a task (when it was created), or 0 if not set. +/// +/// ```c +/// EXTERN_C time_t tc_task_get_entry(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_entry(task: *mut TCTask) -> libc::time_t { wrap(task, |task| libc::time_t::as_ctype(task.get_entry())) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get the wait timestamp for a task, or 0 if not set. +/// +/// ```c +/// EXTERN_C time_t tc_task_get_wait(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_wait(task: *mut TCTask) -> libc::time_t { wrap(task, |task| libc::time_t::as_ctype(task.get_wait())) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get the modified timestamp for a task, or 0 if not set. +/// +/// ```c +/// EXTERN_C time_t tc_task_get_modified(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_modified(task: *mut TCTask) -> libc::time_t { wrap(task, |task| libc::time_t::as_ctype(task.get_modified())) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Check if a task is waiting. +/// +/// ```c +/// EXTERN_C bool tc_task_is_waiting(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_is_waiting(task: *mut TCTask) -> bool { wrap(task, |task| task.is_waiting()) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Check if a task is active (started and not stopped). +/// +/// ```c +/// EXTERN_C bool tc_task_is_active(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { wrap(task, |task| task.is_active()) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Check if a task is blocked (depends on at least one other task). +/// +/// ```c +/// EXTERN_C bool tc_task_is_blocked(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_is_blocked(task: *mut TCTask) -> bool { wrap(task, |task| task.is_blocked()) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Check if a task is blocking (at least one other task depends on it). +/// +/// ```c +/// EXTERN_C bool tc_task_is_blocking(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_is_blocking(task: *mut TCTask) -> bool { wrap(task, |task| task.is_blocking()) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Check if a task has the given tag. If the tag is invalid, this function will return false, as /// that (invalid) tag is not present. No error will be reported via `tc_task_error`. +/// +/// ```c +/// EXTERN_C bool tc_task_has_tag(struct TCTask *task, struct TCString tag); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: TCString) -> bool { // SAFETY: @@ -384,10 +444,16 @@ pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: TCString) -> bo }) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get the tags for the task. /// /// The caller must free the returned TCStringList instance. The TCStringList instance does not /// reference the task and the two may be freed in any order. +/// +/// ```c +/// EXTERN_C struct TCStringList tc_task_get_tags(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_tags(task: *mut TCTask) -> TCStringList { wrap(task, |task| { @@ -405,10 +471,16 @@ pub unsafe extern "C" fn tc_task_get_tags(task: *mut TCTask) -> TCStringList { }) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get the annotations for the task. /// /// The caller must free the returned TCAnnotationList instance. The TCStringList instance does not /// reference the task and the two may be freed in any order. +/// +/// ```c +/// EXTERN_C struct TCAnnotationList tc_task_get_annotations(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_annotations(task: *mut TCTask) -> TCAnnotationList { wrap(task, |task| { @@ -425,9 +497,15 @@ pub unsafe extern "C" fn tc_task_get_annotations(task: *mut TCTask) -> TCAnnotat }) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get the named UDA from the task. /// /// Returns a TCString with NULL ptr field if the UDA does not exist. +/// +/// ```c +/// EXTERN_C struct TCString tc_task_get_uda(struct TCTask *task, struct TCString ns, struct TCString key); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_uda( task: *mut TCTask, @@ -452,9 +530,15 @@ pub unsafe extern "C" fn tc_task_get_uda( }) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get the named legacy UDA from the task. /// /// Returns NULL if the UDA does not exist. +/// +/// ```c +/// EXTERN_C struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_legacy_uda(task: *mut TCTask, key: TCString) -> TCString { wrap(task, |task| { @@ -472,9 +556,15 @@ pub unsafe extern "C" fn tc_task_get_legacy_uda(task: *mut TCTask, key: TCString }) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get all UDAs for this task. /// /// Legacy UDAs are represented with an empty string in the ns field. +/// +/// ```c +/// EXTERN_C struct TCUdaList tc_task_get_udas(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_udas(task: *mut TCTask) -> TCUdaList { wrap(task, |task| { @@ -498,10 +588,16 @@ pub unsafe extern "C" fn tc_task_get_udas(task: *mut TCTask) -> TCUdaList { }) } +#[ffizz_header::item] +#[ffizz(order = 1001)] /// Get all UDAs for this task. /// /// All TCUdas in this list have a NULL ns field. The entire UDA key is /// included in the key field. The caller must free the returned list. +/// +/// ```c +/// EXTERN_C struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_get_legacy_udas(task: *mut TCTask) -> TCUdaList { wrap(task, |task| { @@ -525,7 +621,70 @@ pub unsafe extern "C" fn tc_task_get_legacy_udas(task: *mut TCTask) -> TCUdaList }) } +#[ffizz_header::item] +#[ffizz(order = 1001)] +/// Get all dependencies for a task. +/// +/// ```c +/// EXTERN_C struct TCUuidList tc_task_get_dependencies(struct TCTask *task); +/// ``` +#[no_mangle] +pub unsafe extern "C" fn tc_task_get_dependencies(task: *mut TCTask) -> TCUuidList { + wrap(task, |task| { + let vec: Vec = 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) } + }) +} + +#[ffizz_header::item] +#[ffizz(order = 1002)] +/// Convert an immutable task into a mutable task. +/// +/// The task must not be NULL. It is modified in-place, and becomes mutable. +/// +/// The replica must not be NULL. After this function returns, the replica _cannot be used at all_ +/// until this task is made immutable again. This implies that it is not allowed for more than one +/// task associated with a replica to be mutable at any time. +/// +/// Typical mutation of tasks is bracketed with `tc_task_to_mut` and `tc_task_to_immut`: +/// +/// tc_task_to_mut(task, rep); +/// success = tc_task_done(task); +/// tc_task_to_immut(task, rep); +/// if (!success) { ... } +/// +/// ```c +/// EXTERN_C void tc_task_to_mut(struct TCTask *task, struct TCReplica *tcreplica); +/// ``` +#[no_mangle] +pub unsafe extern "C" fn tc_task_to_mut(task: *mut TCTask, tcreplica: *mut TCReplica) { + // SAFETY: + // - task is not null (promised by caller) + // - task outlives 'a (promised by caller) + let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; + // SAFETY: + // - tcreplica is not NULL (promised by caller) + // - tcreplica lives until later call to to_immut via tc_task_to_immut (promised by caller, + // who cannot call tc_replica_free during this time) + unsafe { tctask.to_mut(tcreplica) }; +} + +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Set a mutable task's status. +/// +/// ```c +/// EXTERN_C TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_set_status(task: *mut TCTask, status: TCStatus) -> TCResult { wrap_mut( @@ -538,7 +697,13 @@ pub unsafe extern "C" fn tc_task_set_status(task: *mut TCTask, status: TCStatus) ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Set a mutable task's property value by name. If value.ptr is NULL, the property is removed. +/// +/// ```c +/// EXTERN_C TCResult tc_task_set_value(struct TCTask *task, struct TCString property, struct TCString value); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_set_value( task: *mut TCTask, @@ -572,7 +737,13 @@ pub unsafe extern "C" fn tc_task_set_value( ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Set a mutable task's description. +/// +/// ```c +/// EXTERN_C TCResult tc_task_set_description(struct TCTask *task, struct TCString description); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_set_description( task: *mut TCTask, @@ -592,8 +763,14 @@ pub unsafe extern "C" fn tc_task_set_description( ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Set a mutable task's entry (creation time). Pass entry=0 to unset /// the entry field. +/// +/// ```c +/// EXTERN_C TCResult tc_task_set_entry(struct TCTask *task, time_t entry); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> TCResult { wrap_mut( @@ -607,7 +784,13 @@ pub unsafe extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_ ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Set a mutable task's wait timestamp. Pass wait=0 to unset the wait field. +/// +/// ```c +/// EXTERN_C TCResult tc_task_set_wait(struct TCTask *task, time_t wait); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCResult { wrap_mut( @@ -621,7 +804,13 @@ pub unsafe extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Set a mutable task's modified timestamp. The value cannot be zero. +/// +/// ```c +/// EXTERN_C TCResult tc_task_set_modified(struct TCTask *task, time_t modified); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_set_modified( task: *mut TCTask, @@ -641,7 +830,13 @@ pub unsafe extern "C" fn tc_task_set_modified( ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Start a task. +/// +/// ```c +/// EXTERN_C TCResult tc_task_start(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult { wrap_mut( @@ -654,7 +849,13 @@ pub unsafe extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult { ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Stop a task. +/// +/// ```c +/// EXTERN_C TCResult tc_task_stop(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult { wrap_mut( @@ -667,7 +868,13 @@ pub unsafe extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult { ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Mark a task as done. +/// +/// ```c +/// EXTERN_C TCResult tc_task_done(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult { wrap_mut( @@ -680,7 +887,13 @@ pub unsafe extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult { ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Mark a task as deleted. +/// +/// ```c +/// EXTERN_C TCResult tc_task_delete(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { wrap_mut( @@ -693,7 +906,13 @@ pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Add a tag to a mutable task. +/// +/// ```c +/// EXTERN_C TCResult tc_task_add_tag(struct TCTask *task, struct TCString tag); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: TCString) -> TCResult { // SAFETY: @@ -711,7 +930,13 @@ pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: TCString) -> TC ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Remove a tag from a mutable task. +/// +/// ```c +/// EXTERN_C TCResult tc_task_remove_tag(struct TCTask *task, struct TCString tag); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: TCString) -> TCResult { // SAFETY: @@ -729,8 +954,14 @@ pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: TCString) -> ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Add an annotation to a mutable task. This call takes ownership of the /// passed annotation, which must not be used after the call returns. +/// +/// ```c +/// EXTERN_C TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annotation); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_add_annotation( task: *mut TCTask, @@ -753,7 +984,13 @@ pub unsafe extern "C" fn tc_task_add_annotation( ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Remove an annotation from a mutable task. +/// +/// ```c +/// EXTERN_C TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_remove_annotation(task: *mut TCTask, entry: i64) -> TCResult { wrap_mut( @@ -766,7 +1003,16 @@ pub unsafe extern "C" fn tc_task_remove_annotation(task: *mut TCTask, entry: i64 ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Set a UDA on a mutable task. +/// +/// ```c +/// EXTERN_C TCResult tc_task_set_uda(struct TCTask *task, +/// struct TCString ns, +/// struct TCString key, +/// struct TCString value); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_set_uda( task: *mut TCTask, @@ -792,7 +1038,13 @@ pub unsafe extern "C" fn tc_task_set_uda( ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Remove a UDA fraom a mutable task. +/// +/// ```c +/// EXTERN_C TCResult tc_task_remove_uda(struct TCTask *task, struct TCString ns, struct TCString key); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_remove_uda( task: *mut TCTask, @@ -815,7 +1067,13 @@ pub unsafe extern "C" fn tc_task_remove_uda( ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Set a legacy UDA on a mutable task. +/// +/// ```c +/// EXTERN_C TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct TCString value); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_set_legacy_uda( task: *mut TCTask, @@ -838,7 +1096,13 @@ pub unsafe extern "C" fn tc_task_set_legacy_uda( ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Remove a UDA fraom a mutable task. +/// +/// ```c +/// EXTERN_C TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_remove_legacy_uda(task: *mut TCTask, key: TCString) -> TCResult { // safety: @@ -855,25 +1119,13 @@ 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 = 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) } - }) -} - +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Add a dependency. +/// +/// ```c +/// EXTERN_C TCResult tc_task_add_dependency(struct TCTask *task, struct TCUuid dep); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_add_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult { // SAFETY: @@ -889,7 +1141,13 @@ pub unsafe extern "C" fn tc_task_add_dependency(task: *mut TCTask, dep: TCUuid) ) } +#[ffizz_header::item] +#[ffizz(order = 1003)] /// Remove a dependency. +/// +/// ```c +/// EXTERN_C TCResult tc_task_remove_dependency(struct TCTask *task, struct TCUuid dep); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_remove_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult { // SAFETY: @@ -905,9 +1163,35 @@ pub unsafe extern "C" fn tc_task_remove_dependency(task: *mut TCTask, dep: TCUui ) } +#[ffizz_header::item] +#[ffizz(order = 1004)] +/// Convert a mutable task into an immutable task. +/// +/// The task must not be NULL. It is modified in-place, and becomes immutable. +/// +/// The replica passed to `tc_task_to_mut` may be used freely after this call. +/// +/// ```c +/// EXTERN_C void tc_task_to_immut(struct TCTask *task); +/// ``` +#[no_mangle] +pub unsafe extern "C" fn tc_task_to_immut(task: *mut TCTask) { + // SAFETY: + // - task is not null (promised by caller) + // - task outlives 'a (promised by caller) + let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; + tctask.to_immut(); +} + +#[ffizz_header::item] +#[ffizz(order = 1005)] /// 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. +/// +/// ```c +/// EXTERN_C struct TCString tc_task_error(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> TCString { // SAFETY: @@ -923,10 +1207,16 @@ pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> TCString { } } +#[ffizz_header::item] +#[ffizz(order = 1006)] /// Free a task. The given task must not be NULL. The task must not be used after this function /// returns, and must not be freed more than once. /// /// If the task is currently mutable, it will first be made immutable. +/// +/// ```c +/// EXTERN_C void tc_task_free(struct TCTask *task); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) { // SAFETY: @@ -940,6 +1230,8 @@ pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) { drop(tctask); } +#[ffizz_header::item] +#[ffizz(order = 1011)] /// Take an item from a TCTaskList. After this call, the indexed item is no longer associated /// with the list and becomes the caller's responsibility, just as if it had been returned from /// `tc_replica_get_task`. @@ -949,6 +1241,10 @@ pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) { /// index is out of bounds, this function will also return NULL. /// /// The passed TCTaskList remains owned by the caller. +/// +/// ```c +/// EXTERN_C struct TCTask *tc_task_list_take(struct TCTaskList *tasks, size_t index); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_list_take(tasks: *mut TCTaskList, index: usize) -> *mut TCTask { // SAFETY: @@ -962,10 +1258,16 @@ pub unsafe extern "C" fn tc_task_list_take(tasks: *mut TCTaskList, index: usize) } } +#[ffizz_header::item] +#[ffizz(order = 1011)] /// 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. +/// +/// ```c +/// EXTERN_C void tc_task_list_free(struct TCTaskList *tasks); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_task_list_free(tasks: *mut TCTaskList) { // SAFETY: @@ -984,7 +1286,7 @@ mod test { let tasks = unsafe { TCTaskList::return_val(Vec::new()) }; assert!(!tasks.items.is_null()); assert_eq!(tasks.len, 0); - assert_eq!(tasks._capacity, 0); + assert_eq!(tasks.capacity, 0); } #[test] @@ -994,6 +1296,6 @@ mod test { unsafe { tc_task_list_free(&mut tasks) }; assert!(tasks.items.is_null()); assert_eq!(tasks.len, 0); - assert_eq!(tasks._capacity, 0); + assert_eq!(tasks.capacity, 0); } } diff --git a/taskchampion/lib/src/uda.rs b/taskchampion/lib/src/uda.rs index 679557504..855547a8c 100644 --- a/taskchampion/lib/src/uda.rs +++ b/taskchampion/lib/src/uda.rs @@ -1,7 +1,22 @@ use crate::traits::*; use crate::types::*; +#[ffizz_header::item] +#[ffizz(order = 500)] +/// ***** TCUda ***** +/// /// TCUda contains the details of a UDA. +/// +/// ```c +/// typedef struct TCUda { +/// // Namespace of the UDA. For legacy UDAs, this may have a NULL ptr field. +/// struct TCString ns; +/// // UDA key. Must not be NULL. +/// struct TCString key; +/// // Content of the UDA. Must not be NULL. +/// struct TCString value; +/// } TCUda; +/// ``` #[repr(C)] #[derive(Default)] pub struct TCUda { @@ -59,9 +74,25 @@ impl PassByValue for TCUda { } } +#[ffizz_header::item] +#[ffizz(order = 510)] +/// ***** TCUdaList ***** +/// /// TCUdaList represents a list of UDAs. /// /// The content of this struct must be treated as read-only. +/// +/// ```c +/// typedef struct TCUdaList { +/// // number of UDAs in items +/// size_t len; +/// // reserved +/// size_t _u1; +/// // Array of UDAs. These remain owned by the TCUdaList instance and will be freed by +/// // tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. +/// struct TCUda *items; +/// } TCUdaList; +/// ``` #[repr(C)] pub struct TCUdaList { /// number of UDAs in items @@ -70,7 +101,7 @@ pub struct TCUdaList { /// total size of items (internal use only) _capacity: libc::size_t, - /// array of UDAs. These remain owned by the TCUdaList instance and will be freed by + /// Array of UDAs. These remain owned by the TCUdaList instance and will be freed by /// tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. items: *mut TCUda, } @@ -100,8 +131,14 @@ impl CList for TCUdaList { } } +#[ffizz_header::item] +#[ffizz(order = 501)] /// Free a TCUda instance. The instance, and the TCStrings it contains, must not be used /// after this call. +/// +/// ```c +/// EXTERN_C void tc_uda_free(struct TCUda *tcuda); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_uda_free(tcuda: *mut TCUda) { debug_assert!(!tcuda.is_null()); @@ -111,10 +148,16 @@ pub unsafe extern "C" fn tc_uda_free(tcuda: *mut TCUda) { drop(uda); } +#[ffizz_header::item] +#[ffizz(order = 511)] /// Free a TCUdaList instance. The instance, and all TCUdas it contains, must not be used after /// this call. /// /// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUdaList. +/// +/// ```c +/// EXTERN_C void tc_uda_list_free(struct TCUdaList *tcudas); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_uda_list_free(tcudas: *mut TCUdaList) { // SAFETY: diff --git a/taskchampion/lib/src/uuid.rs b/taskchampion/lib/src/uuid.rs index 4a41d6180..61d1ed99d 100644 --- a/taskchampion/lib/src/uuid.rs +++ b/taskchampion/lib/src/uuid.rs @@ -3,14 +3,18 @@ use crate::types::*; use libc; 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; - +#[ffizz_header::item] +#[ffizz(order = 300)] +/// ***** TCUuid ***** +/// /// 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. /// -/// cbindgen:field-names=[bytes] +/// ```c +/// typedef struct TCUuid { +/// uint8_t bytes[16]; +/// } TCUuid; +/// ``` #[repr(C)] pub struct TCUuid([u8; 16]); @@ -28,35 +32,41 @@ impl PassByValue for TCUuid { } } -/// Create a new, randomly-generated UUID. -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_new_v4() -> TCUuid { - // SAFETY: - // - value is not allocated - unsafe { TCUuid::return_val(Uuid::new_v4()) } -} - -/// Create a new UUID with the nil value. -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_nil() -> TCUuid { - // SAFETY: - // - value is not allocated - unsafe { TCUuid::return_val(Uuid::nil()) } -} +#[ffizz_header::item] +#[ffizz(order = 301)] +/// Length, in bytes, of the string representation of a UUID (without NUL terminator) +/// +/// ```c +/// #define TC_UUID_STRING_BYTES 36 +/// ``` +// TODO: debug_assert or static_assert this somewhere? +pub const TC_UUID_STRING_BYTES: usize = 36; +#[ffizz_header::item] +#[ffizz(order = 310)] /// TCUuidList represents a list of uuids. /// /// The content of this struct must be treated as read-only. +/// +/// ```c +/// typedef struct TCUuidList { +/// // number of uuids in items +/// size_t len; +/// // reserved +/// size_t _u1; +/// // Array of uuids. This pointer is never NULL for a valid TCUuidList. +/// struct TCUuid *items; +/// } TCUuidList; +/// ``` #[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, + 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. + /// Array of uuids. This pointer is never NULL for a valid TCUuidList. items: *mut TCUuid, } @@ -66,7 +76,7 @@ impl CList for TCUuidList { unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { TCUuidList { len, - _capacity: cap, + capacity: cap, items, } } @@ -81,12 +91,46 @@ impl CList for TCUuidList { } fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self._capacity) + (self.items, self.len, self.capacity) } } +#[ffizz_header::item] +#[ffizz(order = 302)] +/// Create a new, randomly-generated UUID. +/// +/// ```c +/// EXTERN_C struct TCUuid tc_uuid_new_v4(void); +/// ``` +#[no_mangle] +pub unsafe extern "C" fn tc_uuid_new_v4() -> TCUuid { + // SAFETY: + // - value is not allocated + unsafe { TCUuid::return_val(Uuid::new_v4()) } +} + +#[ffizz_header::item] +#[ffizz(order = 302)] +/// Create a new UUID with the nil value. +/// +/// ```c +/// EXTERN_C struct TCUuid tc_uuid_nil(void); +/// ``` +#[no_mangle] +pub unsafe extern "C" fn tc_uuid_nil() -> TCUuid { + // SAFETY: + // - value is not allocated + unsafe { TCUuid::return_val(Uuid::nil()) } +} + +#[ffizz_header::item] +#[ffizz(order = 302)] /// 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. +/// +/// ```c +/// EXTERN_C void tc_uuid_to_buf(struct TCUuid tcuuid, char *buf); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_uuid_to_buf(tcuuid: TCUuid, buf: *mut libc::c_char) { debug_assert!(!buf.is_null()); @@ -104,8 +148,14 @@ pub unsafe extern "C" fn tc_uuid_to_buf(tcuuid: TCUuid, buf: *mut libc::c_char) uuid.as_hyphenated().encode_lower(buf); } +#[ffizz_header::item] +#[ffizz(order = 302)] /// Return the hyphenated string representation of a TCUuid. The returned string /// must be freed with tc_string_free. +/// +/// ```c +/// EXTERN_C struct TCString tc_uuid_to_str(struct TCUuid tcuuid); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> TCString { // SAFETY: @@ -117,8 +167,14 @@ pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> TCString { unsafe { TCString::return_val(s.into()) } } +#[ffizz_header::item] +#[ffizz(order = 302)] /// Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given /// string is not valid. +/// +/// ```c +/// EXTERN_C TCResult tc_uuid_from_str(struct TCString s, struct TCUuid *uuid_out); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_uuid_from_str(s: TCString, uuid_out: *mut TCUuid) -> TCResult { debug_assert!(!s.is_null()); @@ -139,10 +195,16 @@ pub unsafe extern "C" fn tc_uuid_from_str(s: TCString, uuid_out: *mut TCUuid) -> TCResult::Error } +#[ffizz_header::item] +#[ffizz(order = 312)] /// 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. +/// +/// ```c +/// EXTERN_C void tc_uuid_list_free(struct TCUuidList *tcuuids); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) { // SAFETY: @@ -161,7 +223,7 @@ mod test { let tcuuids = unsafe { TCUuidList::return_val(Vec::new()) }; assert!(!tcuuids.items.is_null()); assert_eq!(tcuuids.len, 0); - assert_eq!(tcuuids._capacity, 0); + assert_eq!(tcuuids.capacity, 0); } #[test] @@ -171,6 +233,6 @@ mod test { unsafe { tc_uuid_list_free(&mut tcuuids) }; assert!(tcuuids.items.is_null()); assert_eq!(tcuuids.len, 0); - assert_eq!(tcuuids._capacity, 0); + assert_eq!(tcuuids.capacity, 0); } } diff --git a/taskchampion/lib/src/workingset.rs b/taskchampion/lib/src/workingset.rs index 672194886..ef9ceaf99 100644 --- a/taskchampion/lib/src/workingset.rs +++ b/taskchampion/lib/src/workingset.rs @@ -2,6 +2,10 @@ use crate::traits::*; use crate::types::*; use taskchampion::{Uuid, WorkingSet}; +#[ffizz_header::item] +#[ffizz(order = 1100)] +/// ***** TCWorkingSet ***** +/// /// A TCWorkingSet represents a snapshot of the working set for a replica. It is not automatically /// updated based on changes in the replica. Its lifetime is independent of the replica and it can /// be freed at any time. @@ -23,6 +27,10 @@ use taskchampion::{Uuid, WorkingSet}; /// Once passed to `tc_replica_free`, a `*TCWorkingSet` becomes invalid and must not be used again. /// /// TCWorkingSet is not threadsafe. +/// +/// ```c +/// typedef struct TCWorkingSet TCWorkingSet; +/// ``` pub struct TCWorkingSet(WorkingSet); impl PassByPointer for TCWorkingSet {} @@ -45,20 +53,38 @@ where f(&tcws.0) } +#[ffizz_header::item] +#[ffizz(order = 1101)] /// Get the working set's length, or the number of UUIDs it contains. +/// +/// ```c +/// EXTERN_C size_t tc_working_set_len(struct TCWorkingSet *ws); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_working_set_len(ws: *mut TCWorkingSet) -> usize { wrap(ws, |ws| ws.len()) } +#[ffizz_header::item] +#[ffizz(order = 1101)] /// Get the working set's largest index. +/// +/// ```c +/// EXTERN_C size_t tc_working_set_largest_index(struct TCWorkingSet *ws); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_working_set_largest_index(ws: *mut TCWorkingSet) -> usize { wrap(ws, |ws| ws.largest_index()) } +#[ffizz_header::item] +#[ffizz(order = 1101)] /// Get the UUID for the task at the given index. Returns true if the UUID exists in the working /// set. If not, returns false and does not change uuid_out. +/// +/// ```c +/// EXTERN_C bool tc_working_set_by_index(struct TCWorkingSet *ws, size_t index, struct TCUuid *uuid_out); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_working_set_by_index( ws: *mut TCWorkingSet, @@ -79,8 +105,14 @@ pub unsafe extern "C" fn tc_working_set_by_index( }) } +#[ffizz_header::item] +#[ffizz(order = 1101)] /// Get the working set index for the task with the given UUID. Returns 0 if the task is not in /// the working set. +/// +/// ```c +/// EXTERN_C size_t tc_working_set_by_uuid(struct TCWorkingSet *ws, struct TCUuid uuid); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_working_set_by_uuid(ws: *mut TCWorkingSet, uuid: TCUuid) -> usize { wrap(ws, |ws| { @@ -91,8 +123,14 @@ pub unsafe extern "C" fn tc_working_set_by_uuid(ws: *mut TCWorkingSet, uuid: TCU }) } +#[ffizz_header::item] +#[ffizz(order = 1102)] /// Free a TCWorkingSet. The given value must not be NULL. The value must not be used after this /// function returns, and must not be freed more than once. +/// +/// ```c +/// EXTERN_C void tc_working_set_free(struct TCWorkingSet *ws); +/// ``` #[no_mangle] pub unsafe extern "C" fn tc_working_set_free(ws: *mut TCWorkingSet) { // SAFETY: diff --git a/taskchampion/lib/taskchampion.h b/taskchampion/lib/taskchampion.h index 35782aa28..df2e019a5 100644 --- a/taskchampion/lib/taskchampion.h +++ b/taskchampion/lib/taskchampion.h @@ -1,80 +1,69 @@ -/** - * TaskChampion - * - * This file defines the C interface to libtaskchampion. This is a thin - * wrapper around the Rust `taskchampion` crate. Refer to the documentation - * for that crate at https://docs.rs/taskchampion/latest/taskchampion/ for API - * details. The comments in this file focus mostly on the low-level details of - * passing values to and from TaskChampion. - * - * # Overview - * - * This library defines two major types used to interact with the API, that map directly - * to Rust types. - * - * * TCReplica - see https://docs.rs/taskchampion/latest/taskchampion/struct.Replica.html - * * TCTask - see https://docs.rs/taskchampion/latest/taskchampion/struct.Task.html - * * TCServer - see https://docs.rs/taskchampion/latest/taskchampion/trait.Server.html - * * TCWorkingSet - see https://docs.rs/taskchampion/latest/taskchampion/struct.WorkingSet.html - * - * It also defines a few utility types: - * - * * TCString - a wrapper around both C (NUL-terminated) and Rust (always utf-8) strings. - * * TC…List - a list of objects represented as a C array - * * see below for the remainder - * - * # Safety - * - * Each type contains specific instructions to ensure memory safety. - * The general rules are as follows. - * - * No types in this library are threadsafe. All values should be used in only - * one thread for their entire lifetime. It is safe to use unrelated values in - * different threads (for example, different threads may use different - * TCReplica values concurrently). - * - * ## Pass by Pointer - * - * Several types such as TCReplica and TCString are "opaque" types and always - * handled as pointers in C. The bytes these pointers address are private to - * the Rust implemetation and must not be accessed from C. - * - * Pass-by-pointer values have exactly one owner, and that owner is responsible - * for freeing the value (using a `tc_…_free` function), or transferring - * ownership elsewhere. Except where documented otherwise, when a value is - * passed to C, ownership passes to C as well. When a value is passed to Rust, - * ownership stays with the C code. The exception is TCString, ownership of - * which passes to Rust when it is used as a function argument. - * - * The limited circumstances where one value must not outlive another, due to - * pointer references between them, are documented below. - * - * ## Pass by Value - * - * Types such as TCUuid and TC…List are passed by value, and contain fields - * that are accessible from C. C code is free to access the content of these - * types in a _read_only_ fashion. - * - * Pass-by-value values that contain pointers also have exactly one owner, - * responsible for freeing the value or transferring ownership. The tc_…_free - * functions for these types will replace the pointers with NULL to guard - * against use-after-free errors. The interior pointers in such values should - * never be freed directly (for example, `tc_string_free(tcuda.value)` is an - * error). - * - * TCUuid is a special case, because it does not contain pointers. It can be - * freely copied and need not be freed. - * - * ## Lists - * - * Lists are a special kind of pass-by-value type. Each contains `len` and - * `items`, where `items` is an array of length `len`. Lists, and the values - * in the `items` array, must be treated as read-only. On return from an API - * function, a list's ownership is with the C caller, which must eventually - * free the list. List data must be freed with the `tc_…_list_free` function. - * It is an error to free any value in the `items` array of a list. - */ - +// TaskChampion +// +// This file defines the C interface to libtaskchampion. This is a thin wrapper around the Rust +// `taskchampion` crate. Refer to the documentation for that crate at +// https://docs.rs/taskchampion/latest/taskchampion/ for API details. The comments in this file +// focus mostly on the low-level details of passing values to and from TaskChampion. +// +// # Overview +// +// This library defines two major types used to interact with the API, that map directly to Rust +// types. +// +// * TCReplica - see https://docs.rs/taskchampion/latest/taskchampion/struct.Replica.html * TCTask +// - see https://docs.rs/taskchampion/latest/taskchampion/struct.Task.html * TCServer - see +// https://docs.rs/taskchampion/latest/taskchampion/trait.Server.html * TCWorkingSet - see +// https://docs.rs/taskchampion/latest/taskchampion/struct.WorkingSet.html +// +// It also defines a few utility types: +// +// * TCString - a wrapper around both C (NUL-terminated) and Rust (always utf-8) strings. * +// TC…List - a list of objects represented as a C array * see below for the remainder +// +// # Safety +// +// Each type contains specific instructions to ensure memory safety. The general rules are as +// follows. +// +// No types in this library are threadsafe. All values should be used in only one thread for their +// entire lifetime. It is safe to use unrelated values in different threads (for example, +// different threads may use different TCReplica values concurrently). +// +// ## Pass by Pointer +// +// Several types such as TCReplica and TCString are "opaque" types and always handled as pointers +// in C. The bytes these pointers address are private to the Rust implemetation and must not be +// accessed from C. +// +// Pass-by-pointer values have exactly one owner, and that owner is responsible for freeing the +// value (using a `tc_…_free` function), or transferring ownership elsewhere. Except where +// documented otherwise, when a value is passed to C, ownership passes to C as well. When a value +// is passed to Rust, ownership stays with the C code. The exception is TCString, ownership of +// which passes to Rust when it is used as a function argument. +// +// The limited circumstances where one value must not outlive another, due to pointer references +// between them, are documented below. +// +// ## Pass by Value +// +// Types such as TCUuid and TC…List are passed by value, and contain fields that are accessible +// from C. C code is free to access the content of these types in a _read_only_ fashion. +// +// Pass-by-value values that contain pointers also have exactly one owner, responsible for freeing +// the value or transferring ownership. The tc_…_free functions for these types will replace the +// pointers with NULL to guard against use-after-free errors. The interior pointers in such values +// should never be freed directly (for example, `tc_string_free(tcuda.value)` is an error). +// +// TCUuid is a special case, because it does not contain pointers. It can be freely copied and +// need not be freed. +// +// ## Lists +// +// Lists are a special kind of pass-by-value type. Each contains `len` and `items`, where `items` +// is an array of length `len`. Lists, and the values in the `items` array, must be treated as +// read-only. On return from an API function, a list's ownership is with the C caller, which must +// eventually free the list. List data must be freed with the `tc_…_list_free` function. It is an +// error to free any value in the `items` array of a list. #ifndef TASKCHAMPION_H #define TASKCHAMPION_H @@ -83,20 +72,21 @@ #include #include -/** - * Length, in bytes, of the string representation of a UUID (without NUL terminator) - */ -#define TC_UUID_STRING_BYTES 36 +#ifdef __cplusplus +#define EXTERN_C extern "C" +#else +#define EXTERN_C +#endif // __cplusplus -/** - * A result from a TC operation. Typically if this value is TC_RESULT_ERROR, - * the associated object's `tc_.._error` method will return an error message. - */ +// ***** TCResult ***** +// +// A result from a TC operation. Typically if this value is TC_RESULT_ERROR, +// the associated object's `tc_.._error` method will return an error message. enum TCResult #ifdef __cplusplus : int32_t #endif // __cplusplus - { +{ TC_RESULT_ERROR = -1, TC_RESULT_OK = 0, }; @@ -104,978 +94,752 @@ enum TCResult typedef int32_t TCResult; #endif // __cplusplus -/** - * The status of a task, as defined by the task data model. - */ -typedef enum TCStatus { - TC_STATUS_PENDING, - TC_STATUS_COMPLETED, - TC_STATUS_DELETED, - TC_STATUS_RECURRING, - /** - * Unknown signifies a status in the task DB that was not - * recognized. - */ - TC_STATUS_UNKNOWN, -} TCStatus; - -/** - * A replica represents an instance of a user's task data, providing an easy interface - * for querying and modifying that data. - * - * # Error Handling - * - * When a `tc_replica_..` function that returns a TCResult returns TC_RESULT_ERROR, then - * `tc_replica_error` will return the error message. - * - * # Safety - * - * The `*TCReplica` returned from `tc_replica_new…` functions is owned by the caller and - * must later be freed to avoid a memory leak. - * - * Any function taking a `*TCReplica` requires: - * - the pointer must not be NUL; - * - the pointer must be one previously returned from a tc_… function; - * - the memory referenced by the pointer must never be modified by C code; and - * - except for `tc_replica_free`, ownership of a `*TCReplica` remains with the caller. - * - * Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again. - * - * TCReplicas are not threadsafe. - */ -typedef struct TCReplica TCReplica; - -/** - * TCServer represents an interface to a sync server. Aside from new and free, a server - * has no C-accessible API, but is designed to be passed to `tc_replica_sync`. - * - * ## Safety - * - * TCServer are not threadsafe, and must not be used with multiple replicas simultaneously. - */ -typedef struct TCServer TCServer; - -/** - * A task, as publicly exposed by this library. - * - * A task begins in "immutable" mode. It must be converted to "mutable" mode - * to make any changes, and doing so requires exclusive access to the replica - * until the task is freed or converted back to immutable mode. - * - * An immutable task carries no reference to the replica that created it, and can be used until it - * 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. - * - * When a `tc_task_..` function that returns a TCResult returns TC_RESULT_ERROR, then - * `tc_task_error` will return the error message. - * - * # Safety - * - * A task is an owned object, and must be freed with tc_task_free (or, if part of a list, - * with tc_task_list_free). - * - * Any function taking a `*TCTask` requires: - * - the pointer must not be NUL; - * - the pointer must be one previously returned from a tc_… function; - * - the memory referenced by the pointer must never be modified by C code; and - * - except for `tc_{task,task_list}_free`, ownership of a `*TCTask` remains with the caller. - * - * Once passed to tc_task_free, a `*TCTask` becomes invalid and must not be used again. - * - * TCTasks are not threadsafe. - */ -typedef struct TCTask TCTask; - -/** - * A TCWorkingSet represents a snapshot of the working set for a replica. It is not automatically - * updated based on changes in the replica. Its lifetime is independent of the replica and it can - * be freed at any time. - * - * To iterate over a working set, search indexes 1 through largest_index. - * - * # Safety - * - * The `*TCWorkingSet` returned from `tc_replica_working_set` is owned by the caller and - * must later be freed to avoid a memory leak. Its lifetime is independent of the replica - * from which it was generated. - * - * Any function taking a `*TCWorkingSet` requires: - * - the pointer must not be NUL; - * - the pointer must be one previously returned from `tc_replica_working_set` - * - the memory referenced by the pointer must never be accessed by C code; and - * - except for `tc_replica_free`, ownership of a `*TCWorkingSet` remains with the caller. - * - * Once passed to `tc_replica_free`, a `*TCWorkingSet` becomes invalid and must not be used again. - * - * TCWorkingSet is not threadsafe. - */ -typedef struct TCWorkingSet TCWorkingSet; - -/** - * TCString supports passing strings into and out of the TaskChampion API. - * - * # Rust Strings and C Strings - * - * A Rust string can contain embedded NUL characters, while C considers such a character to mark - * the end of a string. Strings containing embedded NULs cannot be represented as a "C string" - * and must be accessed using `tc_string_content_and_len` and `tc_string_clone_with_len`. In - * general, these two functions should be used for handling arbitrary data, while more convenient - * forms may be used where embedded NUL characters are impossible, such as in static strings. - * - * # UTF-8 - * - * TaskChampion expects all strings to be valid UTF-8. `tc_string_…` functions will fail if given - * a `*TCString` containing invalid UTF-8. - * - * # Safety - * - * The `ptr` field may be checked for NULL, where documentation indicates this is possible. All - * other fields in a TCString are private and must not be used from C. They exist in the struct - * to ensure proper allocation and alignment. - * - * When a `TCString` appears as a return value or output argument, ownership is passed to the - * caller. The caller must pass that ownership back to another function or free the string. - * - * Any function taking a `TCString` requires: - * - the pointer must not be NUL; - * - the pointer must be one previously returned from a tc_… function; and - * - the memory referenced by the pointer must never be modified by C code. - * - * Unless specified otherwise, TaskChampion functions take ownership of a `TCString` when it is - * given as a function argument, and the caller must not use or free TCStrings after passing them - * to such API functions. - * - * A TCString with a NULL `ptr` field need not be freed, although tc_free_string will not fail - * for such a value. - * - * TCString is not threadsafe. - */ +// ***** TCString ***** +// +// TCString supports passing strings into and out of the TaskChampion API. +// +// # Rust Strings and C Strings +// +// A Rust string can contain embedded NUL characters, while C considers such a character to mark +// the end of a string. Strings containing embedded NULs cannot be represented as a "C string" +// and must be accessed using `tc_string_content_and_len` and `tc_string_clone_with_len`. In +// general, these two functions should be used for handling arbitrary data, while more convenient +// forms may be used where embedded NUL characters are impossible, such as in static strings. +// +// # UTF-8 +// +// TaskChampion expects all strings to be valid UTF-8. `tc_string_…` functions will fail if given +// a `*TCString` containing invalid UTF-8. +// +// # Safety +// +// The `ptr` field may be checked for NULL, where documentation indicates this is possible. All +// other fields in a TCString are private and must not be used from C. They exist in the struct +// to ensure proper allocation and alignment. +// +// When a `TCString` appears as a return value or output argument, ownership is passed to the +// caller. The caller must pass that ownership back to another function or free the string. +// +// Any function taking a `TCString` requires: +// - the pointer must not be NUL; +// - the pointer must be one previously returned from a tc_… function; and +// - the memory referenced by the pointer must never be modified by C code. +// +// Unless specified otherwise, TaskChampion functions take ownership of a `TCString` when it is +// given as a function argument, and the caller must not use or free TCStrings after passing them +// to such API functions. +// +// A TCString with a NULL `ptr` field need not be freed, although tc_free_string will not fail +// for such a value. +// +// TCString is not threadsafe. typedef struct TCString { - void *ptr; - size_t _u1; - size_t _u2; - uint8_t _u3; + void *ptr; // opaque, but may be checked for NULL + size_t _u1; // reserved + size_t _u2; // reserved + uint8_t _u3; // reserved } TCString; -/** - * TCAnnotation contains the details of an annotation. - * - * # Safety - * - * An annotation must be initialized from a tc_.. function, and later freed - * with `tc_annotation_free` or `tc_annotation_list_free`. - * - * Any function taking a `*TCAnnotation` requires: - * - the pointer must not be NUL; - * - the pointer must be one previously returned from a tc_… function; - * - the memory referenced by the pointer must never be modified by C code; and - * - ownership transfers to the called function, and the value must not be used - * after the call returns. In fact, the value will be zeroed out to ensure this. - * - * TCAnnotations are not threadsafe. - */ +// 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 +// static string. +// +// NOTE: this function does _not_ take responsibility for freeing the given C string. The +// given string can be freed once the TCString referencing it has been freed. +// +// For example: +// +// ```text +// char *url = get_item_url(..); // dynamically allocate C string +// tc_task_annotate(task, tc_string_borrow(url)); // TCString created, passed, and freed +// free(url); // string is no longer referenced and can be freed +// ``` +EXTERN_C struct TCString tc_string_borrow(const char *cstr); + +// Create a new TCString by cloning the content of the given C string. The resulting TCString +// is independent of the given string, which can be freed or overwritten immediately. +EXTERN_C struct TCString tc_string_clone(const char *cstr); + +// Create a new TCString containing the given string with the given length. This allows creation +// of strings containing embedded NUL characters. As with `tc_string_clone`, the resulting +// TCString is independent of the passed buffer, which may be reused or freed immediately. +// +// The length should _not_ include any trailing NUL. +// +// The given length must be less than half the maximum value of usize. +EXTERN_C struct TCString tc_string_clone_with_len(const char *buf, size_t len); + +// Get the content of the string as a regular C string. The given string must be valid. The +// returned value is NULL if the string contains NUL bytes or (in some cases) invalid UTF-8. The +// returned C string is valid until the TCString is freed or passed to another TC API function. +// +// In general, prefer [`tc_string_content_with_len`] except when it's certain that the string is +// valid and NUL-free. +// +// This function takes the TCString by pointer because it may be modified in-place to add a NUL +// terminator. The pointer must not be NULL. +// +// This function does _not_ take ownership of the TCString. +EXTERN_C const char *tc_string_content(const struct TCString *tcstring); + +// Get the content of the string as a pointer and length. The given string must not be NULL. +// This function can return any string, even one including NUL bytes or invalid UTF-8. The +// returned buffer is valid until the TCString is freed or passed to another TaskChampio +// function. +// +// This function takes the TCString by pointer because it may be modified in-place to add a NUL +// terminator. The pointer must not be NULL. +// +// This function does _not_ take ownership of the TCString. +EXTERN_C const char *tc_string_content_with_len(const struct TCString *tcstring, size_t *len_out); + +// Free a TCString. The given string must not be NULL. The string must not be used +// after this function returns, and must not be freed more than once. +EXTERN_C void tc_string_free(struct TCString *tcstring); + +// ***** TCStringList ***** +// +// TCStringList represents a list of strings. +// +// The content of this struct must be treated as read-only. +typedef struct TCStringList { + // number of strings in items + size_t len; + // reserved + size_t _u1; + // 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. + struct TCString *items; +} TCStringList; + +// 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. +EXTERN_C void tc_string_list_free(struct TCStringList *tcstrings); + +// ***** TCUuid ***** +// +// 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. +typedef struct TCUuid { + uint8_t bytes[16]; +} TCUuid; + +// Length, in bytes, of the string representation of a UUID (without NUL terminator) +#define TC_UUID_STRING_BYTES 36 + +// Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given +// string is not valid. +EXTERN_C TCResult tc_uuid_from_str(struct TCString s, struct TCUuid *uuid_out); + +// Create a new, randomly-generated UUID. +EXTERN_C struct TCUuid tc_uuid_new_v4(void); + +// Create a new UUID with the nil value. +EXTERN_C struct TCUuid tc_uuid_nil(void); + +// 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. +EXTERN_C void tc_uuid_to_buf(struct TCUuid tcuuid, char *buf); + +// Return the hyphenated string representation of a TCUuid. The returned string +// must be freed with tc_string_free. +EXTERN_C struct TCString tc_uuid_to_str(struct TCUuid tcuuid); + +// TCUuidList represents a list of uuids. +// +// The content of this struct must be treated as read-only. +typedef struct TCUuidList { + // number of uuids in items + size_t len; + // reserved + size_t _u1; + // Array of uuids. This pointer is never NULL for a valid TCUuidList. + struct TCUuid *items; +} TCUuidList; + +// 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. +EXTERN_C void tc_uuid_list_free(struct TCUuidList *tcuuids); + +// ***** TCAnnotation ***** +// +// TCAnnotation contains the details of an annotation. +// +// # Safety +// +// An annotation must be initialized from a tc_.. function, and later freed +// with `tc_annotation_free` or `tc_annotation_list_free`. +// +// Any function taking a `*TCAnnotation` requires: +// - the pointer must not be NUL; +// - the pointer must be one previously returned from a tc_… function; +// - the memory referenced by the pointer must never be modified by C code; and +// - ownership transfers to the called function, and the value must not be used +// after the call returns. In fact, the value will be zeroed out to ensure this. +// +// TCAnnotations are not threadsafe. typedef struct TCAnnotation { - /** - * Time the annotation was made. Must be nonzero. - */ + // Time the annotation was made. Must be nonzero. time_t entry; - /** - * Content of the annotation. Must not be NULL. - */ - struct TCString description; + // Content of the annotation. Must not be NULL. + TCString description; } TCAnnotation; -/** - * TCAnnotationList represents a list of annotations. - * - * The content of this struct must be treated as read-only. - */ +// Free a TCAnnotation instance. The instance, and the TCString it contains, must not be used +// after this call. +EXTERN_C void tc_annotation_free(struct TCAnnotation *tcann); + +// ***** TCAnnotationList ***** +// +// TCAnnotationList represents a list of annotations. +// +// The content of this struct must be treated as read-only. typedef struct TCAnnotationList { - /** - * number of annotations in items - */ + // number of annotations in items size_t len; - /** - * total size of items (internal use only) - */ - size_t _capacity; - /** - * array of annotations. these remain owned by the TCAnnotationList instance and will be freed by - * tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList. - */ + // reserved + size_t _u1; + // Array of annotations. These remain owned by the TCAnnotationList instance and will be freed by + // tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList. struct TCAnnotation *items; } TCAnnotationList; -/** - * TCKV contains a key/value pair that is part of a task. - * - * Neither key nor value are ever NULL. They remain owned by the TCKV and - * will be freed when it is freed with tc_kv_list_free. - */ +// Free a TCAnnotationList instance. The instance, and all TCAnnotations it contains, must not be used after +// this call. +// +// When this call returns, the `items` pointer will be NULL, signalling an invalid TCAnnotationList. +EXTERN_C void tc_annotation_list_free(struct TCAnnotationList *tcanns); + +// ***** TCUda ***** +// +// TCUda contains the details of a UDA. +typedef struct TCUda { + // Namespace of the UDA. For legacy UDAs, this may have a NULL ptr field. + struct TCString ns; + // UDA key. Must not be NULL. + struct TCString key; + // Content of the UDA. Must not be NULL. + struct TCString value; +} TCUda; + +// Free a TCUda instance. The instance, and the TCStrings it contains, must not be used +// after this call. +EXTERN_C void tc_uda_free(struct TCUda *tcuda); + +// ***** TCUdaList ***** +// +// TCUdaList represents a list of UDAs. +// +// The content of this struct must be treated as read-only. +typedef struct TCUdaList { + // number of UDAs in items + size_t len; + // reserved + size_t _u1; + // Array of UDAs. These remain owned by the TCUdaList instance and will be freed by + // tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. + struct TCUda *items; +} TCUdaList; + +// Free a TCUdaList instance. The instance, and all TCUdas it contains, must not be used after +// this call. +// +// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUdaList. +EXTERN_C void tc_uda_list_free(struct TCUdaList *tcudas); + +// ***** TCKV ***** +// +// TCKV contains a key/value pair that is part of a task. +// +// Neither key nor value are ever NULL. They remain owned by the TCKV and +// will be freed when it is freed with tc_kv_list_free. typedef struct TCKV { struct TCString key; struct TCString value; } TCKV; -/** - * TCKVList represents a list of key/value pairs. - * - * The content of this struct must be treated as read-only. - */ +// ***** TCKVList ***** +// +// TCKVList represents a list of key/value pairs. +// +// The content of this struct must be treated as read-only. typedef struct TCKVList { - /** - * number of key/value pairs in items - */ + // number of key/value pairs in items size_t len; - /** - * total size of items (internal use only) - */ - size_t _capacity; - /** - * array of TCKV's. these remain owned by the TCKVList instance and will be freed by - * tc_kv_list_free. This pointer is never NULL for a valid TCKVList. - */ + // reserved + size_t _u1; + // Array of TCKV's. These remain owned by the TCKVList instance and will be freed by + // tc_kv_list_free. This pointer is never NULL for a valid TCKVList. struct TCKV *items; } TCKVList; -/** - * TCTaskList represents a list of tasks. - * - * The content of this struct must be treated as read-only: no fields or anything they reference - * should be modified directly by C code. - * - * When an item is taken from this list, its pointer in `items` is set to NULL. - */ -typedef struct TCTaskList { - /** - * number of tasks in items - */ - size_t len; - /** - * total size of items (internal use only) - */ - size_t _capacity; - /** - * 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. - */ - struct TCTask **items; -} TCTaskList; - -/** - * 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. - * - */ -typedef struct TCUuid { - uint8_t bytes[16]; -} TCUuid; - -/** - * TCUuidList represents a list of uuids. - * - * The content of this struct must be treated as read-only. - */ -typedef struct TCUuidList { - /** - * number of uuids in items - */ - size_t len; - /** - * total size of items (internal use only) - */ - size_t _capacity; - /** - * 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. - */ - struct TCUuid *items; -} TCUuidList; - -/** - * TCStringList represents a list of strings. - * - * The content of this struct must be treated as read-only. - */ -typedef struct TCStringList { - /** - * number of strings in items - */ - size_t len; - /** - * total size of items (internal use only) - */ - size_t _capacity; - /** - * 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. - */ - struct TCString *items; -} TCStringList; - -/** - * TCUda contains the details of a UDA. - */ -typedef struct TCUda { - /** - * Namespace of the UDA. For legacy UDAs, this may have a NULL ptr field. - */ - struct TCString ns; - /** - * UDA key. Must not be NULL. - */ - struct TCString key; - /** - * Content of the UDA. Must not be NULL. - */ - struct TCString value; -} TCUda; - -/** - * TCUdaList represents a list of UDAs. - * - * The content of this struct must be treated as read-only. - */ -typedef struct TCUdaList { - /** - * number of UDAs in items - */ - size_t len; - /** - * total size of items (internal use only) - */ - size_t _capacity; - /** - * array of UDAs. These remain owned by the TCUdaList instance and will be freed by - * tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. - */ - struct TCUda *items; -} TCUdaList; +// Free a TCKVList instance. The instance, and all TCKVs it contains, must not be used after +// this call. +// +// When this call returns, the `items` pointer will be NULL, signalling an invalid TCKVList. +EXTERN_C void tc_kv_list_free(struct TCKVList *tckvs); +// ***** TCStatus ***** +// +// The status of a task, as defined by the task data model. #ifdef __cplusplus -extern "C" { +typedef enum TCStatus : int32_t { +#else // __cplusplus +typedef int32_t TCStatus; +enum TCStatus { +#endif // __cplusplus + TC_STATUS_PENDING = 0, + TC_STATUS_COMPLETED = 1, + TC_STATUS_DELETED = 2, + TC_STATUS_RECURRING = 3, + // Unknown signifies a status in the task DB that was not + // recognized. + TC_STATUS_UNKNOWN = -1, +#ifdef __cplusplus +} TCStatus; +#else // __cplusplus +}; #endif // __cplusplus -/** - * Free a TCAnnotation instance. The instance, and the TCString it contains, must not be used - * after this call. - */ -void tc_annotation_free(struct TCAnnotation *tcann); +// ***** TCServer ***** +// +// TCServer represents an interface to a sync server. Aside from new and free, a server +// has no C-accessible API, but is designed to be passed to `tc_replica_sync`. +// +// ## Safety +// +// TCServer are not threadsafe, and must not be used with multiple replicas simultaneously. +typedef struct TCServer TCServer; -/** - * Free a TCAnnotationList instance. The instance, and all TCAnnotations it contains, must not be used after - * this call. - * - * When this call returns, the `items` pointer will be NULL, signalling an invalid TCAnnotationList. - */ -void tc_annotation_list_free(struct TCAnnotationList *tcanns); +// Create a new TCServer that operates locally (on-disk). See the TaskChampion docs for the +// description of the arguments. +// +// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is +// returned. The caller must free this string. +// +// The server must be freed after it is used - tc_replica_sync does not automatically free it. +EXTERN_C struct TCServer *tc_server_new_local(struct TCString server_dir, struct TCString *error_out); -/** - * Free a TCKVList instance. The instance, and all TCKVs it contains, must not be used after - * this call. - * - * When this call returns, the `items` pointer will be NULL, signalling an invalid TCKVList. - */ -void tc_kv_list_free(struct TCKVList *tckvs); - -/** - * Create a new TCReplica with an in-memory database. The contents of the database will be - * lost when it is freed with tc_replica_free. - */ -struct TCReplica *tc_replica_new_in_memory(void); - -/** - * Create a new TCReplica with an on-disk database having the given filename. On error, a string - * is written to the error_out parameter (if it is not NULL) and NULL is returned. The caller - * must free this string. - */ -struct TCReplica *tc_replica_new_on_disk(struct TCString path, - bool create_if_missing, - struct TCString *error_out); - -/** - * Get a list of all tasks in the replica. - * - * Returns a TCTaskList with a NULL items field on error. - */ -struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep); - -/** - * Get a list of all uuids for tasks in the replica. - * - * Returns a TCUuidList with a NULL items field on error. - * - * The caller must free the UUID list with `tc_uuid_list_free`. - */ -struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); - -/** - * Get the current working set for this replica. The resulting value must be freed - * with tc_working_set_free. - * - * Returns NULL on error. - */ -struct TCWorkingSet *tc_replica_working_set(struct TCReplica *rep); - -/** - * Get an existing task by its UUID. - * - * Returns NULL when the task does not exist, and on error. Consult tc_replica_error - * to distinguish the two conditions. - */ -struct TCTask *tc_replica_get_task(struct TCReplica *rep, struct TCUuid tcuuid); - -/** - * Create a new task. The task must not already exist. - * - * Returns the task, or NULL on error. - */ -struct TCTask *tc_replica_new_task(struct TCReplica *rep, - enum TCStatus status, - struct TCString description); - -/** - * Create a new task. The task must not already exist. - * - * Returns the task, or NULL on error. - */ -struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid); - -/** - * Synchronize this replica with a server. - * - * The `server` argument remains owned by the caller, and must be freed explicitly. - */ -TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots); - -/** - * Undo local operations until the most recent UndoPoint. - * - * If undone_out is not NULL, then on success it is set to 1 if operations were undone, or 0 if - * there are no operations that can be done. - */ -TCResult tc_replica_undo(struct TCReplica *rep, int32_t *undone_out); - -/** - * Get the number of local, un-synchronized operations (not including undo points), or -1 on - * error. - */ -int64_t tc_replica_num_local_operations(struct TCReplica *rep); - -/** - * Get the number of undo points (number of undo calls possible), or -1 on error. - */ -int64_t tc_replica_num_undo_points(struct TCReplica *rep); - -/** - * Add an UndoPoint, if one has not already been added by this Replica. This occurs automatically - * when a change is made. The `force` flag allows forcing a new UndoPoint even if one has already - * been created by this Replica, and may be useful when a Replica instance is held for a long time - * and used to apply more than one user-visible change. - */ -TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force); - -/** - * Rebuild this replica's working set, based on whether tasks are pending or not. If `renumber` - * is true, then existing tasks may be moved to new working-set indices; in any case, on - * completion all pending tasks are in the working set and all non- pending tasks are not. - */ -TCResult tc_replica_rebuild_working_set(struct TCReplica *rep, bool renumber); - -/** - * Get the latest error for a replica, or a string with NULL ptr if no error exists. Subsequent - * calls to this function will return NULL. The rep pointer must not be NULL. The caller must - * free the returned string. - */ -struct TCString tc_replica_error(struct TCReplica *rep); - -/** - * Free a replica. The replica may not be used after this function returns and must not be freed - * more than once. - */ -void tc_replica_free(struct TCReplica *rep); - -/** - * Create a new TCServer that operates locally (on-disk). See the TaskChampion docs for the - * description of the arguments. - * - * On error, a string is written to the error_out parameter (if it is not NULL) and NULL is - * returned. The caller must free this string. - * - * The server must be freed after it is used - tc_replica_sync does not automatically free it. - */ -struct TCServer *tc_server_new_local(struct TCString server_dir, struct TCString *error_out); - -/** - * Create a new TCServer that connects to a remote server. See the TaskChampion docs for the - * description of the arguments. - * - * On error, a string is written to the error_out parameter (if it is not NULL) and NULL is - * returned. The caller must free this string. - * - * The server must be freed after it is used - tc_replica_sync does not automatically free it. - */ -struct TCServer *tc_server_new_remote(struct TCString origin, +// Create a new TCServer that connects to a remote server. See the TaskChampion docs for the +// description of the arguments. +// +// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is +// returned. The caller must free this string. +// +// The server must be freed after it is used - tc_replica_sync does not automatically free it. +EXTERN_C struct TCServer *tc_server_new_remote(struct TCString origin, struct TCUuid client_key, struct TCString encryption_secret, struct TCString *error_out); -/** - * Free a server. The server may not be used after this function returns and must not be freed - * more than once. - */ -void tc_server_free(struct TCServer *server); +// Free a server. The server may not be used after this function returns and must not be freed +// more than once. +EXTERN_C void tc_server_free(struct TCServer *server); -/** - * 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 - * static string. - * - * NOTE: this function does _not_ take responsibility for freeing the given C string. The - * given string can be freed once the TCString referencing it has been freed. - * - * For example: - * - * ```text - * char *url = get_item_url(..); // dynamically allocate C string - * tc_task_annotate(task, tc_string_borrow(url)); // TCString created, passed, and freed - * free(url); // string is no longer referenced and can be freed - * ``` - */ -struct TCString tc_string_borrow(const char *cstr); +// ***** TCReplica ***** +// +// A replica represents an instance of a user's task data, providing an easy interface +// for querying and modifying that data. +// +// # Error Handling +// +// When a `tc_replica_..` function that returns a TCResult returns TC_RESULT_ERROR, then +// `tc_replica_error` will return the error message. +// +// # Safety +// +// The `*TCReplica` returned from `tc_replica_new…` functions is owned by the caller and +// must later be freed to avoid a memory leak. +// +// Any function taking a `*TCReplica` requires: +// - the pointer must not be NUL; +// - the pointer must be one previously returned from a tc_… function; +// - the memory referenced by the pointer must never be modified by C code; and +// - except for `tc_replica_free`, ownership of a `*TCReplica` remains with the caller. +// +// Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again. +// +// TCReplicas are not threadsafe. +typedef struct TCReplica TCReplica; -/** - * Create a new TCString by cloning the content of the given C string. The resulting TCString - * is independent of the given string, which can be freed or overwritten immediately. - */ -struct TCString tc_string_clone(const char *cstr); +// Create a new TCReplica with an in-memory database. The contents of the database will be +// lost when it is freed with tc_replica_free. +EXTERN_C struct TCReplica *tc_replica_new_in_memory(void); -/** - * Create a new TCString containing the given string with the given length. This allows creation - * of strings containing embedded NUL characters. As with `tc_string_clone`, the resulting - * TCString is independent of the passed buffer, which may be reused or freed immediately. - * - * The length should _not_ include any trailing NUL. - * - * The given length must be less than half the maximum value of usize. - */ -struct TCString tc_string_clone_with_len(const char *buf, size_t len); +// Create a new TCReplica with an on-disk database having the given filename. On error, a string +// is written to the error_out parameter (if it is not NULL) and NULL is returned. The caller +// must free this string. +EXTERN_C struct TCReplica *tc_replica_new_on_disk(struct TCString path, + bool create_if_missing, + struct TCString *error_out); -/** - * Get the content of the string as a regular C string. The given string must be valid. The - * returned value is NULL if the string contains NUL bytes or (in some cases) invalid UTF-8. The - * returned C string is valid until the TCString is freed or passed to another TC API function. - * - * In general, prefer [`tc_string_content_with_len`] except when it's certain that the string is - * valid and NUL-free. - * - * This function takes the TCString by pointer because it may be modified in-place to add a NUL - * terminator. The pointer must not be NULL. - * - * This function does _not_ take ownership of the TCString. - */ -const char *tc_string_content(const struct TCString *tcstring); +// Add an UndoPoint, if one has not already been added by this Replica. This occurs automatically +// when a change is made. The `force` flag allows forcing a new UndoPoint even if one has already +// been created by this Replica, and may be useful when a Replica instance is held for a long time +// and used to apply more than one user-visible change. +EXTERN_C TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force); -/** - * Get the content of the string as a pointer and length. The given string must not be NULL. - * This function can return any string, even one including NUL bytes or invalid UTF-8. The - * returned buffer is valid until the TCString is freed or passed to another TaskChampio - * function. - * - * This function takes the TCString by pointer because it may be modified in-place to add a NUL - * terminator. The pointer must not be NULL. - * - * This function does _not_ take ownership of the TCString. - */ -const char *tc_string_content_with_len(const struct TCString *tcstring, size_t *len_out); +// Get a list of all uuids for tasks in the replica. +// +// Returns a TCUuidList with a NULL items field on error. +// +// The caller must free the UUID list with `tc_uuid_list_free`. +EXTERN_C struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); -/** - * Free a TCString. The given string must not be NULL. The string must not be used - * after this function returns, and must not be freed more than once. - */ -void tc_string_free(struct TCString *tcstring); +// Get a list of all tasks in the replica. +// +// Returns a TCTaskList with a NULL items field on error. +EXTERN_C struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep); -/** - * 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. - */ -void tc_string_list_free(struct TCStringList *tcstrings); +// Get the latest error for a replica, or a string with NULL ptr if no error exists. Subsequent +// calls to this function will return NULL. The rep pointer must not be NULL. The caller must +// free the returned string. +EXTERN_C struct TCString tc_replica_error(struct TCReplica *rep); -/** - * Convert an immutable task into a mutable task. - * - * The task must not be NULL. It is modified in-place, and becomes mutable. - * - * The replica must not be NULL. After this function returns, the replica _cannot be used at all_ - * until this task is made immutable again. This implies that it is not allowed for more than one - * task associated with a replica to be mutable at any time. - * - * Typical mutation of tasks is bracketed with `tc_task_to_mut` and `tc_task_to_immut`: - * - * ```c - * tc_task_to_mut(task, rep); - * success = tc_task_done(task); - * tc_task_to_immut(task, rep); - * if (!success) { ... } - * ``` - */ -void tc_task_to_mut(struct TCTask *task, struct TCReplica *tcreplica); +// Get an existing task by its UUID. +// +// Returns NULL when the task does not exist, and on error. Consult tc_replica_error +// to distinguish the two conditions. +EXTERN_C struct TCTask *tc_replica_get_task(struct TCReplica *rep, struct TCUuid tcuuid); -/** - * Convert a mutable task into an immutable task. - * - * The task must not be NULL. It is modified in-place, and becomes immutable. - * - * The replica passed to `tc_task_to_mut` may be used freely after this call. - */ -void tc_task_to_immut(struct TCTask *task); +// Create a new task. The task must not already exist. +// +// Returns the task, or NULL on error. +EXTERN_C struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid); -/** - * Get a task's UUID. - */ -struct TCUuid tc_task_get_uuid(struct TCTask *task); +// Create a new task. The task must not already exist. +// +// Returns the task, or NULL on error. +EXTERN_C struct TCTask *tc_replica_new_task(struct TCReplica *rep, + enum TCStatus status, + struct TCString description); -/** - * Get a task's status. - */ -enum TCStatus tc_task_get_status(struct TCTask *task); +// Get the number of local, un-synchronized operations (not including undo points), or -1 on +// error. +EXTERN_C int64_t tc_replica_num_local_operations(struct TCReplica *rep); -/** - * Get the underlying key/value pairs for this task. The returned TCKVList is - * a "snapshot" of the task and will not be updated if the task is subsequently - * modified. It is the caller's responsibility to free the TCKVList. - */ -struct TCKVList tc_task_get_taskmap(struct TCTask *task); +// Get the number of undo points (number of undo calls possible), or -1 on error. +EXTERN_C int64_t tc_replica_num_undo_points(struct TCReplica *rep); -/** - * Get a task's description. - */ -struct TCString tc_task_get_description(struct TCTask *task); +// Rebuild this replica's working set, based on whether tasks are pending or not. If `renumber` +// is true, then existing tasks may be moved to new working-set indices; in any case, on +// completion all pending tasks are in the working set and all non- pending tasks are not. +EXTERN_C TCResult tc_replica_rebuild_working_set(struct TCReplica *rep, bool renumber); -/** - * Get a task property's value, or NULL if the task has no such property, (including if the - * property name is not valid utf-8). - */ -struct TCString tc_task_get_value(struct TCTask *task, struct TCString property); +// Synchronize this replica with a server. +// +// The `server` argument remains owned by the caller, and must be freed explicitly. +EXTERN_C TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots); -/** - * 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); +// Undo local operations until the most recent UndoPoint. +// +// If undone_out is not NULL, then on success it is set to 1 if operations were undone, or 0 if +// there are no operations that can be done. +EXTERN_C TCResult tc_replica_undo(struct TCReplica *rep, int32_t *undone_out); -/** - * Get the wait timestamp for a task, or 0 if not set. - */ -time_t tc_task_get_wait(struct TCTask *task); +// Get the current working set for this replica. The resulting value must be freed +// with tc_working_set_free. +// +// Returns NULL on error. +EXTERN_C struct TCWorkingSet *tc_replica_working_set(struct TCReplica *rep); -/** - * Get the modified timestamp for a task, or 0 if not set. - */ -time_t tc_task_get_modified(struct TCTask *task); +// Free a replica. The replica may not be used after this function returns and must not be freed +// more than once. +EXTERN_C void tc_replica_free(struct TCReplica *rep); -/** - * Check if a task is waiting. - */ -bool tc_task_is_waiting(struct TCTask *task); +// ***** TCTask ***** +// +// A task, as publicly exposed by this library. +// +// A task begins in "immutable" mode. It must be converted to "mutable" mode +// to make any changes, and doing so requires exclusive access to the replica +// until the task is freed or converted back to immutable mode. +// +// An immutable task carries no reference to the replica that created it, and can be used until it +// 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. +// +// When a `tc_task_..` function that returns a TCResult returns TC_RESULT_ERROR, then +// `tc_task_error` will return the error message. +// +// # Safety +// +// A task is an owned object, and must be freed with tc_task_free (or, if part of a list, +// with tc_task_list_free). +// +// Any function taking a `*TCTask` requires: +// - the pointer must not be NUL; +// - the pointer must be one previously returned from a tc_… function; +// - the memory referenced by the pointer must never be modified by C code; and +// - except for `tc_{task,task_list}_free`, ownership of a `*TCTask` remains with the caller. +// +// Once passed to tc_task_free, a `*TCTask` becomes invalid and must not be used again. +// +// TCTasks are not threadsafe. +typedef struct TCTask TCTask; -/** - * Check if a task is active (started and not stopped). - */ -bool tc_task_is_active(struct TCTask *task); +// Get the annotations for the task. +// +// The caller must free the returned TCAnnotationList instance. The TCStringList instance does not +// reference the task and the two may be freed in any order. +EXTERN_C struct TCAnnotationList tc_task_get_annotations(struct TCTask *task); -/** - * Check if a task is blocked (depends on at least one other task). - */ -bool tc_task_is_blocked(struct TCTask *task); +// Get all dependencies for a task. +EXTERN_C struct TCUuidList tc_task_get_dependencies(struct TCTask *task); -/** - * Check if a task is blocking (at least one other task depends on it). - */ -bool tc_task_is_blocking(struct TCTask *task); +// Get a task's description. +EXTERN_C struct TCString tc_task_get_description(struct TCTask *task); -/** - * Check if a task has the given tag. If the tag is invalid, this function will return false, as - * that (invalid) tag is not present. No error will be reported via `tc_task_error`. - */ -bool tc_task_has_tag(struct TCTask *task, struct TCString tag); +// Get the entry timestamp for a task (when it was created), or 0 if not set. +EXTERN_C time_t tc_task_get_entry(struct TCTask *task); -/** - * Get the tags for the task. - * - * The caller must free the returned TCStringList instance. The TCStringList instance does not - * reference the task and the two may be freed in any order. - */ -struct TCStringList tc_task_get_tags(struct TCTask *task); +// Get the named legacy UDA from the task. +// +// Returns NULL if the UDA does not exist. +EXTERN_C struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key); -/** - * Get the annotations for the task. - * - * The caller must free the returned TCAnnotationList instance. The TCStringList instance does not - * reference the task and the two may be freed in any order. - */ -struct TCAnnotationList tc_task_get_annotations(struct TCTask *task); +// Get all UDAs for this task. +// +// All TCUdas in this list have a NULL ns field. The entire UDA key is +// included in the key field. The caller must free the returned list. +EXTERN_C struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task); -/** - * Get the named UDA from the task. - * - * Returns a TCString with NULL ptr field if the UDA does not exist. - */ -struct TCString tc_task_get_uda(struct TCTask *task, struct TCString ns, struct TCString key); +// Get the modified timestamp for a task, or 0 if not set. +EXTERN_C time_t tc_task_get_modified(struct TCTask *task); -/** - * Get the named legacy UDA from the task. - * - * Returns NULL if the UDA does not exist. - */ -struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key); +// Get a task's status. +EXTERN_C enum TCStatus tc_task_get_status(struct TCTask *task); -/** - * Get all UDAs for this task. - * - * Legacy UDAs are represented with an empty string in the ns field. - */ -struct TCUdaList tc_task_get_udas(struct TCTask *task); +// Get the tags for the task. +// +// The caller must free the returned TCStringList instance. The TCStringList instance does not +// reference the task and the two may be freed in any order. +EXTERN_C struct TCStringList tc_task_get_tags(struct TCTask *task); -/** - * Get all UDAs for this task. - * - * All TCUdas in this list have a NULL ns field. The entire UDA key is - * included in the key field. The caller must free the returned list. - */ -struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task); +// Get the underlying key/value pairs for this task. The returned TCKVList is +// a "snapshot" of the task and will not be updated if the task is subsequently +// modified. It is the caller's responsibility to free the TCKVList. +EXTERN_C struct TCKVList tc_task_get_taskmap(struct TCTask *task); -/** - * Set a mutable task's status. - */ -TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); +// Get the named UDA from the task. +// +// Returns a TCString with NULL ptr field if the UDA does not exist. +EXTERN_C struct TCString tc_task_get_uda(struct TCTask *task, struct TCString ns, struct TCString key); -/** - * Set a mutable task's property value by name. If value.ptr is NULL, the property is removed. - */ -TCResult tc_task_set_value(struct TCTask *task, struct TCString property, struct TCString value); +// Get all UDAs for this task. +// +// Legacy UDAs are represented with an empty string in the ns field. +EXTERN_C struct TCUdaList tc_task_get_udas(struct TCTask *task); -/** - * Set a mutable task's description. - */ -TCResult tc_task_set_description(struct TCTask *task, struct TCString description); +// Get a task's UUID. +EXTERN_C struct TCUuid tc_task_get_uuid(struct TCTask *task); -/** - * 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); +// Get a task property's value, or NULL if the task has no such property, (including if the +// property name is not valid utf-8). +EXTERN_C struct TCString tc_task_get_value(struct TCTask *task, struct TCString property); -/** - * Set a mutable task's wait timestamp. Pass wait=0 to unset the wait field. - */ -TCResult tc_task_set_wait(struct TCTask *task, time_t wait); +// Get the wait timestamp for a task, or 0 if not set. +EXTERN_C time_t tc_task_get_wait(struct TCTask *task); -/** - * Set a mutable task's modified timestamp. The value cannot be zero. - */ -TCResult tc_task_set_modified(struct TCTask *task, time_t modified); +// Check if a task has the given tag. If the tag is invalid, this function will return false, as +// that (invalid) tag is not present. No error will be reported via `tc_task_error`. +EXTERN_C bool tc_task_has_tag(struct TCTask *task, struct TCString tag); -/** - * Start a task. - */ -TCResult tc_task_start(struct TCTask *task); +// Check if a task is active (started and not stopped). +EXTERN_C bool tc_task_is_active(struct TCTask *task); -/** - * Stop a task. - */ -TCResult tc_task_stop(struct TCTask *task); +// Check if a task is blocked (depends on at least one other task). +EXTERN_C bool tc_task_is_blocked(struct TCTask *task); -/** - * Mark a task as done. - */ -TCResult tc_task_done(struct TCTask *task); +// Check if a task is blocking (at least one other task depends on it). +EXTERN_C bool tc_task_is_blocking(struct TCTask *task); -/** - * Mark a task as deleted. - */ -TCResult tc_task_delete(struct TCTask *task); +// Check if a task is waiting. +EXTERN_C bool tc_task_is_waiting(struct TCTask *task); -/** - * Add a tag to a mutable task. - */ -TCResult tc_task_add_tag(struct TCTask *task, struct TCString tag); +// Convert an immutable task into a mutable task. +// +// The task must not be NULL. It is modified in-place, and becomes mutable. +// +// The replica must not be NULL. After this function returns, the replica _cannot be used at all_ +// until this task is made immutable again. This implies that it is not allowed for more than one +// task associated with a replica to be mutable at any time. +// +// Typical mutation of tasks is bracketed with `tc_task_to_mut` and `tc_task_to_immut`: +// +// tc_task_to_mut(task, rep); +// success = tc_task_done(task); +// tc_task_to_immut(task, rep); +// if (!success) { ... } +EXTERN_C void tc_task_to_mut(struct TCTask *task, struct TCReplica *tcreplica); -/** - * Remove a tag from a mutable task. - */ -TCResult tc_task_remove_tag(struct TCTask *task, struct TCString tag); +// Add an annotation to a mutable task. This call takes ownership of the +// passed annotation, which must not be used after the call returns. +EXTERN_C TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annotation); -/** - * Add an annotation to a mutable task. This call takes ownership of the - * passed annotation, which must not be used after the call returns. - */ -TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annotation); +// Add a dependency. +EXTERN_C TCResult tc_task_add_dependency(struct TCTask *task, struct TCUuid dep); -/** - * Remove an annotation from a mutable task. - */ -TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry); +// Add a tag to a mutable task. +EXTERN_C TCResult tc_task_add_tag(struct TCTask *task, struct TCString tag); -/** - * Set a UDA on a mutable task. - */ -TCResult tc_task_set_uda(struct TCTask *task, +// Mark a task as deleted. +EXTERN_C TCResult tc_task_delete(struct TCTask *task); + +// Mark a task as done. +EXTERN_C TCResult tc_task_done(struct TCTask *task); + +// Remove an annotation from a mutable task. +EXTERN_C TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry); + +// Remove a dependency. +EXTERN_C TCResult tc_task_remove_dependency(struct TCTask *task, struct TCUuid dep); + +// Remove a UDA fraom a mutable task. +EXTERN_C TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key); + +// Remove a tag from a mutable task. +EXTERN_C TCResult tc_task_remove_tag(struct TCTask *task, struct TCString tag); + +// Remove a UDA fraom a mutable task. +EXTERN_C TCResult tc_task_remove_uda(struct TCTask *task, struct TCString ns, struct TCString key); + +// Set a mutable task's description. +EXTERN_C 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. +EXTERN_C TCResult tc_task_set_entry(struct TCTask *task, time_t entry); + +// Set a legacy UDA on a mutable task. +EXTERN_C TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct TCString value); + +// Set a mutable task's modified timestamp. The value cannot be zero. +EXTERN_C TCResult tc_task_set_modified(struct TCTask *task, time_t modified); + +// Set a mutable task's status. +EXTERN_C TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); + +// Set a UDA on a mutable task. +EXTERN_C TCResult tc_task_set_uda(struct TCTask *task, struct TCString ns, struct TCString key, struct TCString value); -/** - * Remove a UDA fraom a mutable task. - */ -TCResult tc_task_remove_uda(struct TCTask *task, struct TCString ns, struct TCString key); +// Set a mutable task's property value by name. If value.ptr is NULL, the property is removed. +EXTERN_C TCResult tc_task_set_value(struct TCTask *task, struct TCString property, struct TCString value); -/** - * Set a legacy UDA on a mutable task. - */ -TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct TCString value); +// Set a mutable task's wait timestamp. Pass wait=0 to unset the wait field. +EXTERN_C TCResult tc_task_set_wait(struct TCTask *task, time_t wait); -/** - * Remove a UDA fraom a mutable task. - */ -TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key); +// Start a task. +EXTERN_C TCResult tc_task_start(struct TCTask *task); -/** - * Get all dependencies for a task. - */ -struct TCUuidList tc_task_get_dependencies(struct TCTask *task); +// Stop a task. +EXTERN_C TCResult tc_task_stop(struct TCTask *task); -/** - * Add a dependency. - */ -TCResult tc_task_add_dependency(struct TCTask *task, struct TCUuid dep); +// Convert a mutable task into an immutable task. +// +// The task must not be NULL. It is modified in-place, and becomes immutable. +// +// The replica passed to `tc_task_to_mut` may be used freely after this call. +EXTERN_C void tc_task_to_immut(struct TCTask *task); -/** - * 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 +// caller must free the returned string. +EXTERN_C struct TCString tc_task_error(struct TCTask *task); -/** - * 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. - */ -struct TCString tc_task_error(struct TCTask *task); +// Free a task. The given task must not be NULL. The task must not be used after this function +// returns, and must not be freed more than once. +// +// If the task is currently mutable, it will first be made immutable. +EXTERN_C void tc_task_free(struct TCTask *task); -/** - * Free a task. The given task must not be NULL. The task must not be used after this function - * returns, and must not be freed more than once. - * - * If the task is currently mutable, it will first be made immutable. - */ -void tc_task_free(struct TCTask *task); +// ***** TCTaskList ***** +// +// TCTaskList represents a list of tasks. +// +// The content of this struct must be treated as read-only: no fields or anything they reference +// should be modified directly by C code. +// +// When an item is taken from this list, its pointer in `items` is set to NULL. +typedef struct TCTaskList { + // number of tasks in items + size_t len; + // reserved + size_t _u1; + // 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. + // Pointers in the array may be NULL after `tc_task_list_take`. + struct TCTask **items; +} TCTaskList; -/** - * Take an item from a TCTaskList. After this call, the indexed item is no longer associated - * with the list and becomes the caller's responsibility, just as if it had been returned from - * `tc_replica_get_task`. - * - * The corresponding element in the `items` array will be set to NULL. If that field is already - * NULL (that is, if the item has already been taken), this function will return NULL. If the - * index is out of bounds, this function will also return NULL. - * - * The passed TCTaskList remains owned by the caller. - */ -struct TCTask *tc_task_list_take(struct TCTaskList *tasks, size_t index); +// 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. +EXTERN_C void tc_task_list_free(struct TCTaskList *tasks); -/** - * 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. - */ -void tc_task_list_free(struct TCTaskList *tasks); +// Take an item from a TCTaskList. After this call, the indexed item is no longer associated +// with the list and becomes the caller's responsibility, just as if it had been returned from +// `tc_replica_get_task`. +// +// The corresponding element in the `items` array will be set to NULL. If that field is already +// NULL (that is, if the item has already been taken), this function will return NULL. If the +// index is out of bounds, this function will also return NULL. +// +// The passed TCTaskList remains owned by the caller. +EXTERN_C struct TCTask *tc_task_list_take(struct TCTaskList *tasks, size_t index); -/** - * Free a TCUda instance. The instance, and the TCStrings it contains, must not be used - * after this call. - */ -void tc_uda_free(struct TCUda *tcuda); +// ***** TCWorkingSet ***** +// +// A TCWorkingSet represents a snapshot of the working set for a replica. It is not automatically +// updated based on changes in the replica. Its lifetime is independent of the replica and it can +// be freed at any time. +// +// To iterate over a working set, search indexes 1 through largest_index. +// +// # Safety +// +// The `*TCWorkingSet` returned from `tc_replica_working_set` is owned by the caller and +// must later be freed to avoid a memory leak. Its lifetime is independent of the replica +// from which it was generated. +// +// Any function taking a `*TCWorkingSet` requires: +// - the pointer must not be NUL; +// - the pointer must be one previously returned from `tc_replica_working_set` +// - the memory referenced by the pointer must never be accessed by C code; and +// - except for `tc_replica_free`, ownership of a `*TCWorkingSet` remains with the caller. +// +// Once passed to `tc_replica_free`, a `*TCWorkingSet` becomes invalid and must not be used again. +// +// TCWorkingSet is not threadsafe. +typedef struct TCWorkingSet TCWorkingSet; -/** - * Free a TCUdaList instance. The instance, and all TCUdas it contains, must not be used after - * this call. - * - * When this call returns, the `items` pointer will be NULL, signalling an invalid TCUdaList. - */ -void tc_uda_list_free(struct TCUdaList *tcudas); +// Get the UUID for the task at the given index. Returns true if the UUID exists in the working +// set. If not, returns false and does not change uuid_out. +EXTERN_C bool tc_working_set_by_index(struct TCWorkingSet *ws, size_t index, struct TCUuid *uuid_out); -/** - * Create a new, randomly-generated UUID. - */ -struct TCUuid tc_uuid_new_v4(void); +// Get the working set index for the task with the given UUID. Returns 0 if the task is not in +// the working set. +EXTERN_C size_t tc_working_set_by_uuid(struct TCWorkingSet *ws, struct TCUuid uuid); -/** - * Create a new UUID with the nil value. - */ -struct TCUuid tc_uuid_nil(void); +// Get the working set's largest index. +EXTERN_C size_t tc_working_set_largest_index(struct TCWorkingSet *ws); -/** - * 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. - */ -void tc_uuid_to_buf(struct TCUuid tcuuid, char *buf); +// Get the working set's length, or the number of UUIDs it contains. +EXTERN_C size_t tc_working_set_len(struct TCWorkingSet *ws); -/** - * Return the hyphenated string representation of a TCUuid. The returned string - * must be freed with tc_string_free. - */ -struct TCString tc_uuid_to_str(struct TCUuid tcuuid); - -/** - * Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given - * string is not valid. - */ -TCResult tc_uuid_from_str(struct TCString s, struct TCUuid *uuid_out); - -/** - * 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. - */ -void tc_uuid_list_free(struct TCUuidList *tcuuids); - -/** - * Get the working set's length, or the number of UUIDs it contains. - */ -size_t tc_working_set_len(struct TCWorkingSet *ws); - -/** - * Get the working set's largest index. - */ -size_t tc_working_set_largest_index(struct TCWorkingSet *ws); - -/** - * Get the UUID for the task at the given index. Returns true if the UUID exists in the working - * set. If not, returns false and does not change uuid_out. - */ -bool tc_working_set_by_index(struct TCWorkingSet *ws, size_t index, struct TCUuid *uuid_out); - -/** - * Get the working set index for the task with the given UUID. Returns 0 if the task is not in - * the working set. - */ -size_t tc_working_set_by_uuid(struct TCWorkingSet *ws, struct TCUuid uuid); - -/** - * Free a TCWorkingSet. The given value must not be NULL. The value must not be used after this - * function returns, and must not be freed more than once. - */ -void tc_working_set_free(struct TCWorkingSet *ws); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus +// Free a TCWorkingSet. The given value must not be NULL. The value must not be used after this +// function returns, and must not be freed more than once. +EXTERN_C void tc_working_set_free(struct TCWorkingSet *ws); #endif /* TASKCHAMPION_H */ diff --git a/taskchampion/xtask/Cargo.toml b/taskchampion/xtask/Cargo.toml index 84940010d..99362defc 100644 --- a/taskchampion/xtask/Cargo.toml +++ b/taskchampion/xtask/Cargo.toml @@ -5,4 +5,4 @@ edition = "2018" [dependencies] anyhow = "1.0" -cbindgen = "0.24.3" +taskchampion-lib = { path = "../lib" } diff --git a/taskchampion/xtask/src/main.rs b/taskchampion/xtask/src/main.rs index 73ea71119..47909b4c8 100644 --- a/taskchampion/xtask/src/main.rs +++ b/taskchampion/xtask/src/main.rs @@ -3,8 +3,9 @@ //! At the moment it is very simple, but if this grows more subcommands then //! it will be sensible to use `clap` or another similar library. -use cbindgen::*; use std::env; +use std::fs::File; +use std::io::Write; use std::path::PathBuf; pub fn main() -> anyhow::Result<()> { @@ -18,32 +19,13 @@ pub fn main() -> anyhow::Result<()> { /// `cargo xtask codegen` /// -/// This uses cbindgen to generate `lib/taskchampion.h`. +/// This uses ffizz-header to generate `lib/taskchampion.h`. fn codegen() -> anyhow::Result<()> { let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let workspace_dir = manifest_dir.parent().unwrap(); let lib_crate_dir = workspace_dir.join("lib"); - - Builder::new() - .with_crate(&lib_crate_dir) - .with_config(Config { - header: Some(include_str!("../../lib/header-intro.h").into()), - language: Language::C, - include_guard: Some("TASKCHAMPION_H".into()), - cpp_compat: true, - sys_includes: vec!["stdbool.h".into(), "stdint.h".into(), "time.h".into()], - usize_is_size_t: true, - no_includes: true, - enumeration: EnumConfig { - // this appears to still default to true for C - enum_class: false, - ..Default::default() - }, - ..Default::default() - }) - .generate() - .expect("Unable to generate bindings") - .write_to_file(lib_crate_dir.join("taskchampion.h")); + let mut file = File::create(lib_crate_dir.join("taskchampion.h")).unwrap(); + write!(&mut file, "{}", ::taskchampion_lib::generate_header()).unwrap(); Ok(()) }